home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 42 / Amiga Format AFCD42 (Issue 126, Aug 1999).iso / -serious- / comms / other / slrn / slrn_src / src / art.c next >
C/C++ Source or Header  |  1999-05-14  |  186KB  |  8,342 lines

  1. /* -*- mode: C; mode: fold; -*- */
  2. /* Copyright (c) 1998 John E. Davis (davis@space.mit.edu)
  3.  *
  4.  * This file is part of slrn.
  5.  *
  6.  * Slrn is free software; you can redistribute it and/or modify it
  7.  * under the terms of the GNU General Public License as published by the
  8.  * Free Software Foundation; either version 2, or (at your option) any
  9.  * later version.
  10.  * 
  11.  * Slrn is distributed in the hope that it will be useful, but WITHOUT
  12.  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13.  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14.  * for more details.
  15.  * 
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with Slrn; see the file COPYING.  If not, write to the Free
  18.  * Software Foundation, 59 Temple Place - Suite 330, 
  19.  * Boston, MA  02111-1307, USA.
  20.  */
  21.  
  22. #include "config.h"
  23. #include "slrnfeat.h"
  24.  
  25. /*{{{ system include files */
  26. #include <stdio.h>
  27. #include <string.h>
  28. #include <time.h>
  29.  
  30.  
  31. #ifdef HAVE_UNISTD_H
  32. # include <unistd.h>
  33. #endif
  34.  
  35. #ifdef HAVE_STDLIB_H
  36. # include <stdlib.h>
  37. #endif
  38.  
  39. #include <ctype.h>
  40. #include <slang.h>
  41. #include "jdmacros.h"
  42.  
  43.  
  44. #ifndef isalpha
  45. # define isalpha(x) \
  46.   ((((x) <= 'Z') && ((x) >= 'A')) \
  47.    || (((x) <= 'z') && ((x) >= 'a')))
  48. #endif
  49.  
  50. #ifndef isspace
  51. # define isspace(x) (((x) == ' ') || ((x) == '\t'))
  52. #endif
  53.  
  54. /*}}}*/
  55. /*{{{ slrn include files */
  56. #include "slrn.h"
  57. #include "group.h"
  58. #include "server.h"
  59. #include "art.h"
  60. #include "misc.h"
  61. #include "post.h"
  62. /* #include "clientlib.h" */
  63. #include "startup.h"
  64. #include "hash.h" 
  65. #include "score.h"
  66. #include "menu.h"
  67. #include "util.h"
  68. #include "xover.h"
  69. #include "chmap.h"
  70. #include "print.h"
  71.  
  72. #if SLRN_HAS_UUDEVIEW
  73. # include <uudeview.h>
  74. #endif
  75. #include "uudecode.h"
  76.  
  77. #if SLRN_HAS_MIME 
  78. # include "mime.h"
  79. #endif
  80.  
  81. #if SLRN_HAS_GROUPLENS
  82. # include "grplens.h"
  83. #endif
  84.  
  85. /*}}}*/
  86.  
  87. /*{{{ extern Global variables  */
  88.  
  89. SLKeyMap_List_Type *Slrn_Article_Keymap;
  90. char *Slrn_X_Browser;
  91. char *Slrn_NonX_Browser;
  92. char *Slrn_Quote_String;
  93. char *Slrn_Save_Directory;
  94. char *Slrn_Header_Help_Line;
  95. char *Slrn_Art_Help_Line;
  96. char *Slrn_Followup_Custom_Headers;
  97. char *Slrn_Reply_Custom_Headers;
  98.  
  99. #if SLRN_HAS_TILDE_FEATURE
  100. int Slrn_Use_Tildes = 1;
  101. #endif
  102.  
  103. int Slrn_Startup_With_Article = 0;
  104. int Slrn_Followup_Strip_Sig;
  105.  
  106. int Slrn_Query_Next_Article = 1;
  107. int Slrn_Query_Next_Group = 1;
  108. int Slrn_Auto_CC_To_Poster = 0;
  109. int Slrn_Score_After_XOver;
  110. int Slrn_Use_Tmpdir = 0;
  111. int Slrn_Threads_Visible = 0;
  112. int Slrn_Use_Header_Numbers = 1;
  113. #if SLRN_HAS_SORT_BY_SCORE
  114. int Slrn_Display_Score;
  115. #endif
  116. int Slrn_High_Score_Min = 1;
  117. int Slrn_Low_Score_Max = 0;
  118. int Slrn_Kill_Score_Max = -9999;
  119. int Slrn_Article_Window_Border = 0;
  120. int Slrn_Reads_Per_Update = 50;
  121. int Slrn_Sig_Is_End_Of_Article = 0;
  122. #if SLRN_HAS_SPOILERS
  123. int Slrn_Spoiler_Char = 42;
  124. int Slrn_Spoiler_Display_Mode = 1;
  125. #endif
  126. int Slrn_New_Subject_Breaks_Threads = 0;
  127.  
  128. int Slrn_Simulate_Graphic_Chars = 0;
  129. int Slrn_Del_Article_Upon_Read = 1;
  130.  
  131. char *Slrn_Current_Group_Name;
  132. Slrn_Header_Type *Slrn_First_Header;
  133. Slrn_Header_Type *Slrn_Current_Header;
  134.  
  135. /* range of articles on server for current group */
  136. int Slrn_Server_Min, Slrn_Server_Max;
  137.  
  138. /* Sorting mode:
  139.  *   0 No sorting
  140.  *   1 sort by threads
  141.  *   2 sort by subject
  142.  *   3 sort by subject and threads
  143.  *   4 sort by score
  144.  *   5 sort by score and threads
  145.  *   6 sort by score then by subject
  146.  *   7 thread, then sort by score then subject
  147.  *   8 sort by date
  148.  */
  149. #define SORT_BY_THREADS  1
  150. #define SORT_BY_SUBJECT  2
  151. #if SLRN_HAS_SORT_BY_SCORE
  152. # define SORT_BY_SCORE   4
  153. #endif
  154. #define SORT_BY_DATE    8
  155.  
  156. int Slrn_Sorting_Mode = (SORT_BY_THREADS|SORT_BY_SUBJECT);
  157.  
  158. #define ALL_THREAD_FLAGS (FAKE_PARENT | FAKE_CHILDREN | FAKE_HEADER_HIGH_SCORE)
  159.  
  160. Slrn_Article_Line_Type *Slrn_Article_Lines;
  161.  
  162. /*}}}*/
  163. /*{{{ static global variables */
  164. static SLscroll_Window_Type Slrn_Article_Window;
  165. static SLscroll_Window_Type Slrn_Header_Window;
  166.  
  167. static int Header_Window_Nrows;
  168. static unsigned int Number_Killed;
  169. static unsigned int Number_High_Scored;
  170. static unsigned int Number_Low_Scored;
  171. static int User_Aborted_Group_Read;
  172. #if SLRN_HAS_SPOILERS
  173. static Slrn_Header_Type *Spoilers_Visible;
  174. #endif
  175.  
  176. #if SLRN_HAS_GROUPLENS
  177. static int Num_GroupLens_Rated = -1;
  178. #endif
  179. static Slrn_Header_Type *Mark_Header;  /* header with mark set */
  180.  
  181. static Slrn_Group_Type *Current_Group; /* group being processed */
  182.  
  183. static int Total_Num_Headers;           /* headers retrieved from server.  This
  184.                     * number is used only by update meters */
  185. static Slrn_Header_Type *Headers;
  186. static int Last_Cursor_Row;           /* row where --> cursor last was */
  187. static Slrn_Header_Type *Header_Showing;    /* header whose article is selected */
  188. static Slrn_Header_Type *Last_Read_Header;
  189. static int Article_Visible;           /* non-zero if article window is visible */
  190. static char Output_Filename[256];
  191. static int Headers_Threaded;
  192.  
  193. /* If +1, threads are all collapsed.  If zero, none are.  If -1, some may
  194.  * be and some may not.  In other words, if -1, this variable should not
  195.  * be trusted.
  196.  */
  197. static int Threads_Collapsed = 0;
  198.  
  199. static int Graphic_LTee_Char = SLSMG_LTEE_CHAR;
  200. static int Graphic_UTee_Char = SLSMG_UTEE_CHAR;
  201. static int Graphic_LLCorn_Char = SLSMG_LLCORN_CHAR;
  202. static int Graphic_HLine_Char = SLSMG_HLINE_CHAR;
  203. static int Graphic_VLine_Char = SLSMG_VLINE_CHAR;
  204. static int Graphic_ULCorn_Char = SLSMG_ULCORN_CHAR;
  205.  
  206. #define ALT_CHAR_SET_MODE    1
  207. #define SIMULATED_CHAR_SET_MODE    2
  208.  
  209. static int Graphic_Chars_Mode = ALT_CHAR_SET_MODE;
  210.  
  211. #define HEADER_TABLE_SIZE 1250
  212. static Slrn_Header_Type *Header_Table[HEADER_TABLE_SIZE];
  213. static int Headers_Hidden_Mode = 1;
  214. static int Quotes_Hidden = 0;
  215. static char *Super_Cite_Regexp = "^[^A-Za-z0-9]*\"\\([-_a-zA-Z/]+\\)\" == .+";
  216. static int Do_Rot13;
  217. static int Perform_Scoring;
  218. static int Largest_Header_Number;
  219. static int Article_Window_Nrows;
  220. static int Article_Window_HScroll;
  221. static int Article_Wrapped;
  222. static SLKeymap_Function_Type *Art_Functions_Ptr;
  223. static Slrn_Article_Line_Type *Article_Current_Line;
  224. static int Header_Window_HScroll;
  225.  
  226. static Slrn_Header_Type *At_End_Of_Article;
  227. /* If this variable is NULL, then we are not at the end of an article.  If it
  228.  * points at the current article, the we are at the end of that article.
  229.  * If it points anywhere else, ignore it.
  230.  */
  231.  
  232. /*}}}*/
  233. /*{{{ static function declarations */
  234.  
  235. static void toggle_header_formats (void);
  236. static void slrn_art_hangup (int);
  237. static void sort_threads (void);
  238. static void hide_quotes (void);
  239. static void wrap_article (void);
  240. static void unwrap_article (void);
  241. static void art_update_screen (void);
  242. static void art_next_unread (void);
  243. static void thread_headers (void);
  244. static void sort_by_threads (void);
  245. static void sort_by_sorting_mode (void);
  246. static int select_article (int);
  247. static Slrn_Article_Line_Type *unwrap_line (Slrn_Article_Line_Type *);
  248. static void quick_help (void);
  249. static void for_this_tree (Slrn_Header_Type *, void (*)(Slrn_Header_Type *));
  250. static void find_non_hidden_header (void);
  251.  
  252. static void skip_to_next_group (void);
  253.  
  254. #if SLRN_HAS_SPOILERS
  255. static void show_spoilers (void);
  256. #endif
  257. /*}}}*/
  258.  
  259. /*{{{ utility functions */
  260.  
  261. static int is_blank_line (unsigned char *b) /*{{{*/
  262. {
  263.    b = (unsigned char *) slrn_skip_whitespace ((char *) b);
  264.    return (*b == 0);
  265. }
  266.  
  267. /*}}}*/
  268.  
  269. static char *map_char_to_string (int ch) /*{{{*/
  270. {
  271.    static char charbuf[8];
  272.    switch (ch)
  273.      {
  274.       case ' ':     return "SPACE";
  275.       case '\t':     return "TAB";
  276.       case '\r':     return "RETURN";
  277.       case 27:         return "ESCAPE";
  278.       case 127:     return "DELETE";
  279.       case 8:         return "BACKSPACE";
  280.      }
  281.  
  282.    if (ch < 32)
  283.      {
  284.     sprintf (charbuf, "Ctrl-%c", ch + '@');
  285.     return charbuf;
  286.      }
  287.    
  288.    sprintf (charbuf, "'%c'", ch);
  289.    return charbuf;
  290. }
  291.  
  292. /*}}}*/
  293.  
  294. /*}}}*/
  295.  
  296.  
  297. /*{{{ SIGWINCH and window resizing functions */
  298. static void art_winch (void) /*{{{*/
  299. {
  300.    static int rows = 0;
  301.    
  302. #if SLRN_HAS_SLANG
  303.    /* Check to see if rows is still 0. If so, this is the first
  304.     * call here and we should run resize_screen_hook to allow it
  305.     * to change the initial Article_Window_Nrows.
  306.     */
  307.    if (rows == 0)
  308.      {
  309.     rows = SLtt_Screen_Rows;
  310.     SLang_run_hooks ("resize_screen_hook", 0);
  311.      }
  312. #endif
  313.  
  314.    if ((rows != SLtt_Screen_Rows) 
  315.        || (Article_Window_Nrows <= 1)
  316.        || (Article_Window_Nrows > SLtt_Screen_Rows - 3))
  317.      {
  318.     rows = SLtt_Screen_Rows;
  319.     
  320.     Article_Window_Nrows = (3 * rows) / 4;
  321.     if (rows <= 28)
  322.       Article_Window_Nrows = rows - 8;
  323.      }
  324.  
  325.    Header_Window_Nrows = rows - 3;
  326.    if (Article_Visible)
  327.      Header_Window_Nrows -= Article_Window_Nrows;
  328.  
  329.    if (Header_Window_Nrows < 0) Header_Window_Nrows = 1;
  330.    if (Article_Window_Nrows < 0) Article_Window_Nrows = 1;
  331.    
  332.    Slrn_Article_Window.nrows = Article_Window_Nrows;
  333.    Slrn_Header_Window.nrows = Header_Window_Nrows;
  334.  
  335.    if (Slrn_Wrap_Mode & 0x4) wrap_article ();
  336.  
  337.    Slrn_Full_Screen_Update = 1;
  338. }
  339.  
  340. /*}}}*/
  341.  
  342.  
  343.  
  344. static void set_article_visibility (int visible)
  345. {
  346.    if (visible == Article_Visible)
  347.      return;
  348.    Article_Visible = visible;
  349.    art_winch ();
  350. }
  351.  
  352. static void art_winch_sig (int old_r, int old_c) /*{{{*/
  353. {
  354.    (void) old_c;
  355.    if (old_r != SLtt_Screen_Rows)
  356.      Article_Window_Nrows = 0;
  357.    
  358.    art_winch ();
  359. }
  360.  
  361. /*}}}*/
  362.  
  363. static void shrink_window (void) /*{{{*/
  364. {
  365.    if (Article_Visible == 0) return;
  366.    Article_Window_Nrows++;
  367.    art_winch ();
  368. }
  369.  
  370. /*}}}*/
  371.  
  372. static void enlarge_window (void) /*{{{*/
  373. {
  374.    if (Article_Visible == 0) return;
  375.    Article_Window_Nrows--;
  376.    art_winch ();
  377. }
  378.  
  379. /*}}}*/
  380.  
  381. void slrn_set_article_window_size (int nrows)
  382. {
  383.    Article_Window_Nrows = nrows;
  384.    art_winch ();
  385. }
  386.  
  387. /*}}}*/
  388. /*{{{ header hash functions */
  389. static void delete_hash_table (void) /*{{{*/
  390. {
  391.    SLMEMSET ((char *) Header_Table, 0, sizeof (Header_Table));
  392. }
  393.  
  394. /*}}}*/
  395.  
  396. static void make_hash_table (void) /*{{{*/
  397. {
  398.    Slrn_Header_Type *h;
  399.    delete_hash_table ();
  400.    h = Slrn_First_Header;
  401.    while (h != NULL)
  402.      {
  403.     h->hash_next = Header_Table[h->hash % HEADER_TABLE_SIZE];
  404.     Header_Table[h->hash % HEADER_TABLE_SIZE] = h;
  405.     h = h->real_next;
  406.      }
  407. }
  408.  
  409. /*}}}*/
  410.  
  411. static void free_header (Slrn_Header_Type *h)
  412. {
  413.    slrn_free (h->subject);
  414.    slrn_free ((char *) h);
  415. }
  416.  
  417. /*}}}*/
  418.  
  419. /*{{{ article line specific functions */
  420.  
  421. static void find_article_line_num (void) /*{{{*/
  422. {
  423.    Slrn_Article_Line_Type *l;
  424.    
  425.    /* Make sure Article_Current_Line is not hidden */
  426.    l = Article_Current_Line;
  427.    while ((l != NULL) && (l->flags & HIDDEN_LINE))
  428.      l = l->prev;
  429.    if (l == NULL)
  430.      l = Article_Current_Line;
  431.    while ((l != NULL) && (l->flags & HIDDEN_LINE))
  432.      l = l->next;
  433.    
  434.    Article_Current_Line = l;
  435.  
  436.    Slrn_Article_Window.current_line = (SLscroll_Type *) Article_Current_Line;
  437.    
  438.    /* Force current line to be at top of window */
  439.    Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
  440.    Slrn_Full_Screen_Update = 1;
  441.    
  442.    SLscroll_find_line_num (&Slrn_Article_Window);
  443. }
  444.  
  445. /*}}}*/
  446.  
  447. static void init_article_window_struct (void) /*{{{*/
  448. {
  449.    Slrn_Article_Window.hidden_mask = HIDDEN_LINE;
  450.    Slrn_Article_Window.current_line = (SLscroll_Type *) Article_Current_Line;
  451.    Slrn_Article_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
  452.    Slrn_Article_Window.lines = (SLscroll_Type *) Slrn_Article_Lines;
  453.    Slrn_Article_Window.border = Slrn_Article_Window_Border;
  454.    art_winch ();               /* set nrows element */
  455.    find_article_line_num ();
  456. }
  457.  
  458. /*}}}*/
  459.  
  460. static void free_article (void) /*{{{*/
  461. {
  462.    Slrn_Article_Line_Type *l, *next;
  463.    
  464.    l = Slrn_Article_Lines;
  465.    while (l != NULL)
  466.      {
  467.     SLFREE (l->buf);
  468.     next = l->next;
  469.     SLFREE (l);
  470.     l = next;
  471.      }
  472.    
  473.    SLMEMSET((char *) &Slrn_Article_Window, 0, sizeof(SLscroll_Window_Type));
  474.    
  475.    Article_Current_Line = Slrn_Article_Lines = NULL;
  476.    Header_Showing = NULL;
  477.    Article_Wrapped = 0;
  478.    set_article_visibility (0);
  479. }
  480.  
  481. /*}}}*/
  482.  
  483. typedef struct _Visible_Header_Type
  484. {
  485.    char *header;
  486.    unsigned int len;
  487.    struct _Visible_Header_Type *next;
  488. }
  489. Visible_Header_Type;
  490.  
  491. Visible_Header_Type *Visible_Headers;
  492.  
  493. static void free_visible_header_list (void)
  494. {
  495.    while (Visible_Headers != NULL)
  496.      {
  497.     Visible_Header_Type *next;
  498.     next = Visible_Headers->next;
  499.     SLang_free_slstring (Visible_Headers->header);   /* NULL ok */
  500.     SLfree ((char *) Visible_Headers);
  501.     Visible_Headers = next;
  502.      }
  503. }
  504.  
  505. int slrn_set_visible_headers (char *headers)
  506. {
  507.    char buf[256];
  508.    unsigned int nth;
  509.  
  510.    free_visible_header_list ();
  511.  
  512.    nth = 0;
  513.    while (-1 != SLextract_list_element (headers, nth, ',', buf, sizeof(buf)))
  514.      {
  515.     Visible_Header_Type *next;
  516.     
  517.     next = (Visible_Header_Type *) SLmalloc (sizeof (Visible_Header_Type));
  518.     if (next == NULL)
  519.       return -1;
  520.     memset ((char *) next, 0, sizeof(Visible_Header_Type));
  521.     if (NULL == (next->header = SLang_create_slstring (buf)))
  522.       {
  523.          SLfree ((char *) next);
  524.          return -1;
  525.       }
  526.     next->len = strlen (buf);
  527.     next->next = Visible_Headers;
  528.     Visible_Headers = next;
  529.     
  530.     nth++;
  531.      }
  532.    return 0;
  533. }
  534.  
  535. /* Does NOT update line numbers */
  536. static void hide_art_headers (void) /*{{{*/
  537. {
  538.    Slrn_Article_Line_Type *l = Slrn_Article_Lines;
  539.    char ch;
  540.    
  541.    while ((l != NULL) && ((ch = *l->buf) != 0))
  542.      {
  543.     int hide_header;
  544.     Visible_Header_Type *v;
  545.  
  546.     l->flags = HEADER_LINE;
  547.     ch |= 0x20;
  548.  
  549.     hide_header = 1;
  550.  
  551.     v = Visible_Headers;
  552.     while (v != NULL)
  553.       {
  554.          char chv = (0x20 | v->header[0]);
  555.          
  556.          if ((chv == ch)
  557.          && (0 == slrn_case_strncmp ((unsigned char *)l->buf, 
  558.                          (unsigned char *)v->header, 
  559.                          v->len)))
  560.            {
  561.           hide_header = 0;
  562.           break;
  563.            }
  564.          
  565.          v = v->next;
  566.       }
  567.  
  568.     do
  569.       {
  570.          l->flags |= HEADER_LINE;
  571.          if (Headers_Hidden_Mode && hide_header)
  572.            {
  573.           l->flags |= HIDDEN_LINE;
  574.            }
  575.          else l->flags &= ~HIDDEN_LINE;
  576.          
  577.          l = l->next;
  578.       }
  579.     while ((l != NULL) && ((*l->buf == ' ') || (*l->buf == '\t')));
  580.      }
  581.  
  582.    Slrn_Full_Screen_Update = 1;
  583. }
  584.  
  585. /*}}}*/
  586.  
  587. static void skip_quoted_text (void) /*{{{*/
  588. {
  589.    Slrn_Article_Line_Type *l = Article_Current_Line;
  590.    
  591.    /* look for a quoted line */
  592.    while (l != NULL)
  593.      {
  594.     if ((l->flags & HIDDEN_LINE) == 0)
  595.       {
  596.          Article_Current_Line = l;
  597.          if (l->flags & QUOTE_LINE) break;
  598.       }
  599.     l = l->next;
  600.      }
  601.    
  602.    /* Now we are either at the end of the buffer or on a quote line. Skip
  603.     * past other quote lines.
  604.     */
  605.    
  606.    if (l == NULL)
  607.      {
  608.     find_article_line_num ();
  609.     return;
  610.      }
  611.    
  612.    l = l->next;
  613.    
  614.    while (l != NULL)
  615.      {
  616.     if (l->flags & HIDDEN_LINE)
  617.       {
  618.          l = l->next;
  619.          continue;
  620.       }
  621.     Article_Current_Line = l;
  622.     if ((l->flags & QUOTE_LINE) == 0)
  623.       {
  624.          /* Check to see if it is blank */
  625.          if (is_blank_line ((unsigned char *) l->buf) == 0) break;
  626.       }
  627.     l = l->next;
  628.      }
  629.    
  630.    find_article_line_num ();
  631. }
  632.  
  633. /*}}}*/
  634.  
  635. static void skip_digest_forward (void) /*{{{*/
  636. {
  637.    Slrn_Article_Line_Type *l;
  638.    int num_passes;
  639.    
  640.    /* We are looking for:
  641.     * <blank line>  (actually, most digests do not have this-- even the FAQ that suggests it!!)
  642.     * ------------------------------
  643.     * <blank line>
  644.     * Subject: something
  645.     *
  646.     * In fact, most digests do not conform to this.  So, I will look for:
  647.     * <blank line>
  648.     * Subject: something
  649.     *
  650.     * Actually, most faqs, etc... do not support this.  So, look for any line
  651.     * beginning with a number on second pass.  Sigh.
  652.     */
  653.    num_passes = 0;
  654.    while (num_passes < 2)
  655.      {
  656.     l = Article_Current_Line;
  657.     if (l != NULL) l = l->next;
  658.     
  659.     while (l != NULL)
  660.       {
  661.          char ch;
  662.          char *buf;
  663.          
  664.          if ((l->flags & HIDDEN_LINE) || (l->flags & HEADER_LINE))
  665.            {
  666.           l = l->next;
  667.           continue;
  668.            }
  669.          
  670.          buf = l->buf;
  671.          if (num_passes == 0)
  672.            {
  673.           if ((strncmp ("Subject:", buf, 8))
  674.               || (((ch = buf[8]) != ' ') && (ch != '\t')))
  675.             {
  676.                l = l->next;
  677.                continue;
  678.             }
  679.            }
  680.          else
  681.            {
  682.           ch = *buf;
  683.           if ((ch > '9') || (ch < '0'))
  684.             {
  685.                l = l->next;
  686.                continue;
  687.             }
  688.            }
  689.          
  690.          Article_Current_Line = l;
  691.          find_article_line_num ();
  692.          return;
  693.       }
  694.     num_passes++;
  695.      }
  696.    slrn_error ("No next digest.");
  697. }
  698.  
  699. /*}}}*/
  700.  
  701. static int try_supercite (void) /*{{{*/
  702. {
  703.    Slrn_Article_Line_Type *l = Slrn_Article_Lines, *last, *lsave;
  704.    static unsigned char compiled_pattern_buf[256];
  705.    static SLRegexp_Type re;
  706.    unsigned char *b;
  707.    int count;
  708.    char name[32];
  709.    unsigned int len;
  710.    int ret;
  711.    
  712.    re.pat = (unsigned char *) Super_Cite_Regexp;
  713.    re.buf = compiled_pattern_buf;
  714.    re.case_sensitive = 1;
  715.    re.buf_len = sizeof (compiled_pattern_buf);
  716.    
  717.    /* skip header --- I should look for Xnewsreader: gnus */
  718.    while ((l != NULL) && (*l->buf != 0)) l = l->next;
  719.    
  720.    if ((*compiled_pattern_buf == 0) && SLang_regexp_compile (&re))
  721.      return -1;
  722.    
  723.    /* look at the first 15 lines on first attempt.
  724.     * After that, scan the whole buffer looking for more citations */
  725.    count = 15;
  726.    lsave = l;
  727.    ret = -1;
  728.    while (1)
  729.      {
  730.     while (count && (l != NULL))
  731.       {
  732.          if ((l->flags & QUOTE_LINE) == 0)
  733.            {
  734.           if (NULL != slrn_regexp_match (&re, l->buf))
  735.             {
  736.                l->flags |= QUOTE_LINE;
  737.                break;
  738.             }
  739.            }
  740.          l = l->next;
  741.          count--;
  742.       }
  743.     
  744.     if ((l == NULL) || (count == 0)) return ret;
  745.     
  746.     /* Now find out what is used for citing. */
  747.     b = (unsigned char *) l->buf + re.beg_matches[1];
  748.     len = re.end_matches[1];
  749.     if (len > sizeof (name) - 2) return ret;
  750.     
  751.     ret = 0;
  752.     strncpy (name, (char *) b, len); name[len] = 0;
  753.     /* strcat (name, ">");
  754.      len++; */
  755.     
  756.     while (l != NULL)
  757.       {
  758.          unsigned char ch;
  759.          
  760.          b = (unsigned char *) l->buf;
  761.          last = l;
  762.          l = l->next;
  763.          if (last->flags & QUOTE_LINE) continue;
  764.          
  765.          b = (unsigned char *) slrn_skip_whitespace ((char *) b);
  766.  
  767.          if (!strncmp ((char *) b, name, len)
  768.          && (((ch = b[len] | 0x20) < 'a')
  769.              || (ch > 'z')))
  770.            {
  771.           last->flags |= QUOTE_LINE;
  772.           
  773.           while (l != NULL)
  774.             {
  775.                b = (unsigned char *) slrn_skip_whitespace (l->buf);
  776.                if (strncmp ((char *) b, name, len)
  777.                || (((ch = b[len] | 0x20) >= 'a')
  778.                    && (ch <= 'z')))
  779.              break;
  780.                l->flags |= QUOTE_LINE;
  781.                l = l->next;
  782.             }
  783.            }
  784.       }
  785.     count = -1;
  786.     l = lsave;
  787.      }
  788. }
  789.  
  790. /*}}}*/
  791.  
  792. #if SLRN_HAS_SPOILERS
  793. static void mark_spoilers (void) /*{{{*/
  794. {
  795.    Slrn_Article_Line_Type *l = Slrn_Article_Lines;
  796.    int spoiler = 0;
  797.    
  798.    /* skip header */
  799.    while ((l != NULL) && (l->flags & HEADER_LINE))
  800.      l = l->next;
  801.    
  802.    while (l != NULL)
  803.      {
  804.     if ((l->buf[0] == 12) && (l->buf[1] == 0))
  805.       {
  806.          spoiler = 1;
  807.       }
  808.     else if (spoiler)
  809.       {
  810.          l->flags |= SPOILER_LINE;
  811.       }
  812.     l = l->next;
  813.      }   
  814. }
  815.  
  816. /*}}}*/
  817. #endif
  818.  
  819. static void mark_quotes (void) /*{{{*/
  820. {
  821.    Slrn_Article_Line_Type *l, *last;
  822.    unsigned char *b;
  823.    SLRegexp_Type **r;
  824.    
  825.    if (0 == try_supercite ())
  826.      {
  827.     /* return; */
  828.      }
  829.    
  830.    if (Slrn_Ignore_Quote_Regexp[0] == NULL) return;
  831.    
  832.    /* skip header */
  833.    l = Slrn_Article_Lines;
  834.    while ((l != NULL) && (*l->buf != 0)) l = l->next;
  835.    
  836.    while (l != NULL)
  837.      {
  838.     unsigned int min_len;
  839.     b = (unsigned char *) l->buf;
  840.     min_len = strlen ((char *) b);
  841.     last = l;
  842.     
  843.     l = l->next;               /* if first time through, this skips
  844.                     * blank line at at last header.
  845.                     */
  846.     
  847.     r = Slrn_Ignore_Quote_Regexp;
  848.     
  849.     while (*r != NULL)
  850.       {
  851.          SLRegexp_Type *re;
  852.          
  853.          re = *r;
  854.          if ((re->min_length <= min_len) &&
  855.          (NULL != SLang_regexp_match (b, min_len, re)))
  856.            {
  857.           last->flags |= QUOTE_LINE;
  858.           while (l != NULL)
  859.             {
  860.                b = (unsigned char *) l->buf;
  861.                min_len = strlen ((char *) b);
  862.                if ((re->min_length <= min_len)
  863.                && (NULL == SLang_regexp_match (b, min_len, re)))
  864.              {
  865.                 
  866.                 /* Here it might be a good idea to add:
  867.                  * l = l->prev;
  868.                  */
  869.                 break;
  870.              }
  871.                l->flags |= QUOTE_LINE;
  872.                l = l->next;
  873.             }
  874.           break;
  875.            }
  876.          r++;
  877.       }
  878.      }
  879. }
  880.  
  881. /*}}}*/
  882.  
  883. static void mark_signature (void) /*{{{*/
  884. {
  885.    Slrn_Article_Line_Type *l = Slrn_Article_Lines;
  886.    int nmax = 10;
  887.    
  888.    if (l == NULL) return;
  889.    /* go to end of article */
  890.    while (l->next != NULL) l = l->next;
  891.    
  892.    /* skip back untill "-- " seen.  Assume that it is not more than
  893.     * 10 lines long.
  894.     */
  895.    
  896.    while ((l != NULL) && nmax--
  897.       && ((*l->buf != '-')
  898.           || strcmp (l->buf, "-- ")))
  899.      l = l->prev;
  900.    
  901.    if (nmax == -1) return;
  902.    
  903.    while (l != NULL)
  904.      {
  905.         l->flags |= SIGNATURE_LINE;
  906.         l->flags &= ~(
  907.               QUOTE_LINE  /* if in a signature, not a quote */
  908. #if SLRN_HAS_SPOILERS
  909.               | SPOILER_LINE     /* not a spoiler */
  910. #endif
  911.               );
  912.     l = l->next;
  913.      }
  914. }
  915.  
  916. /*}}}*/
  917.  
  918.    
  919. char *slrn_extract_header (char *hdr, unsigned int len) /*{{{*/
  920. {
  921.    Slrn_Article_Line_Type *l;
  922.    
  923.    if (Slrn_Current_Header == NULL)
  924.      return NULL;
  925.  
  926.    if ((len > 2) && (hdr[len-1] == ' ') && (hdr[len-2] == ':'))
  927.      len--;
  928.  
  929.    if (Slrn_Current_Header != Header_Showing)
  930.      {
  931.     if (0 == slrn_case_strncmp ((unsigned char *)"From: ", (unsigned char *)hdr, len))
  932.       return slrn_skip_whitespace (Slrn_Current_Header->from);
  933.     if (0 == slrn_case_strncmp ((unsigned char *)"Subject: ", (unsigned char *)hdr, len))
  934.       return Slrn_Current_Header->subject;
  935.     if (0 == slrn_case_strncmp ((unsigned char *)"Message-Id: ", (unsigned char *)hdr, len))
  936.       return slrn_skip_whitespace (Slrn_Current_Header->msgid);
  937.     if (0 == slrn_case_strncmp ((unsigned char *)"Date: ", (unsigned char *)hdr, len))
  938.       return Slrn_Current_Header->date;
  939.     if (0 == slrn_case_strncmp ((unsigned char *)"References: ", (unsigned char *)hdr, len))
  940.       return slrn_skip_whitespace (Slrn_Current_Header->refs);
  941.     if (0 == slrn_case_strncmp ((unsigned char *)"Xref: ", (unsigned char *)hdr, len))
  942.       return Slrn_Current_Header->xref;
  943.     if (0 == slrn_case_strncmp ((unsigned char *)"Lines: ", (unsigned char *)hdr, len))
  944.       {
  945.          static char lines_buf[16];
  946.          sprintf (lines_buf, "%d", Slrn_Current_Header->lines);
  947.          return lines_buf;
  948.       }
  949.     return NULL;
  950.      }
  951.    
  952.    l = Slrn_Article_Lines;
  953.  
  954.    while ((l != NULL)
  955.       && (*l->buf != 0))
  956.      {
  957.     if (0 == slrn_case_strncmp ((unsigned char *) hdr,
  958.                     (unsigned char *) l->buf, len))
  959.       {
  960.          char *result;
  961.  
  962.          if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
  963.            unwrap_line (l->next);
  964.  
  965.          /* Return the data after the colon */
  966.          result = slrn_strchr (l->buf, ':');
  967.          if (result == NULL) 
  968.            result = l->buf + len;
  969.          else result += 1;
  970.  
  971.          return slrn_skip_whitespace (result);
  972.       }
  973.     l = l->next;
  974.      }
  975.    return NULL;
  976. }
  977.  
  978. /*}}}*/
  979.  
  980. /*{{{ wrap article functions  */
  981. /* The input line is assumed to be the first wrapped portion of a line.  For
  982.  * example, if a series of lines denoted as A/B is wrapped: A0/A1/A2/B0/B1,
  983.  * then to unwrap A, A1 is passed and B0 is returned.
  984.  */
  985. static Slrn_Article_Line_Type *unwrap_line (Slrn_Article_Line_Type *l) /*{{{*/
  986. {
  987.    char *b;
  988.    Slrn_Article_Line_Type *next, *ll;
  989.          
  990.    ll = l->prev;
  991.    b = ll->buf;
  992.    do 
  993.      {
  994.     b += strlen (b);
  995.     strcpy (b, l->buf + 1);   /* skip the space at beginning of
  996.                    * the wrapped line. */
  997.     next = l->next;
  998.     SLFREE (l->buf);
  999.     SLFREE (l);
  1000.     if (l == Article_Current_Line) Article_Current_Line = ll;
  1001.     l = next;
  1002.      }
  1003.    while ((l != NULL) && (l->flags & WRAPPED_LINE));
  1004.          
  1005.    ll->next = l;
  1006.    if (l != NULL) l->prev = ll;
  1007.    return l;
  1008. }
  1009.  
  1010. /*}}}*/
  1011.  
  1012. int Slrn_Wrap_Mode = 3;
  1013.  
  1014. static void unwrap_article (void)
  1015. {
  1016.    Slrn_Article_Line_Type *l;
  1017.  
  1018.    if (Article_Wrapped == 0) return;
  1019.    Article_Wrapped = 0;
  1020.  
  1021.    if (Header_Showing == NULL) return;
  1022.    
  1023.    l = Slrn_Article_Lines;
  1024.    while (l != NULL)
  1025.      {
  1026.     if (l->flags & WRAPPED_LINE)
  1027.       l = unwrap_line (l);
  1028.     else l = l->next;
  1029.      }
  1030.    
  1031.    Slrn_Full_Screen_Update = 1;
  1032.    find_article_line_num ();
  1033. }
  1034.  
  1035. static void wrap_article (void) /*{{{*/
  1036. {
  1037.    unsigned int len;
  1038.    unsigned char *buf, ch;
  1039.    Slrn_Article_Line_Type *l;
  1040.    unsigned int wrap_mode = Slrn_Wrap_Mode;
  1041.  
  1042.    if (Header_Showing == NULL) return;
  1043.    
  1044.    (void) unwrap_article ();
  1045.  
  1046.    l = Slrn_Article_Lines;
  1047.  
  1048.    while (l != NULL)
  1049.      {
  1050.     unsigned char header_char_delimiter = 0;
  1051.  
  1052.     if (l->flags & HEADER_LINE) 
  1053.       {
  1054.          if ((wrap_mode & 1) == 0)
  1055.            {
  1056.           l = l->next;
  1057.           continue;
  1058.            }
  1059.  
  1060.          if (0 == slrn_case_strncmp ((unsigned char *)"Path: ",
  1061.                      (unsigned char *)l->buf, 6))
  1062.            header_char_delimiter = '!';
  1063.          else if (0 == slrn_case_strncmp ((unsigned char *) "Newsgroups: ",
  1064.                           (unsigned char *)l->buf, 12))
  1065.            header_char_delimiter = ',';
  1066.       }
  1067.     else if (l->flags & QUOTE_LINE)
  1068.       {
  1069.          if ((wrap_mode & 2) == 0)
  1070.            {
  1071.           l = l->next;
  1072.           continue;
  1073.            }
  1074.       }
  1075.  
  1076.     len = 0;
  1077.     buf = (unsigned char *) l->buf;
  1078.     ch = *buf;
  1079.     while (ch != 0)
  1080.       {
  1081.          if ((ch == '\t') && (SLsmg_Tab_Width > 0))
  1082.            {
  1083.           len += SLsmg_Tab_Width;
  1084.           len -= len % SLsmg_Tab_Width;
  1085.            }
  1086.          else if (((ch >= ' ') && (ch < 127))
  1087.               || (ch >= (unsigned char) SLsmg_Display_Eight_Bit))
  1088.            len++;
  1089.          else
  1090.            {
  1091.           len += 2;
  1092.           if (ch & 0x80) len++;
  1093.            }
  1094.  
  1095.          if (len > (unsigned int) SLtt_Screen_Cols)
  1096.            {
  1097.           Slrn_Article_Line_Type *new_l;
  1098.           unsigned char *buf0, *lbuf;
  1099.  
  1100.           /* Try to break the line on a word boundary.  
  1101.            * For now, I will only break on space characters.
  1102.            */
  1103.           buf0 = buf;
  1104.           lbuf = (unsigned char *) l->buf;
  1105.  
  1106.           lbuf += 1;           /* avoid space at beg of line */
  1107.  
  1108.           while (buf0 > lbuf)
  1109.             {
  1110.                if ((*buf0 == ' ') || (*buf0 == '\t')
  1111.                || (header_char_delimiter 
  1112.                    && (*buf0 == header_char_delimiter)))
  1113.              {
  1114.                 buf = buf0;
  1115.                 break;
  1116.              }
  1117.                buf0--;
  1118.             }
  1119.  
  1120.           if (buf0 == lbuf)
  1121.             {
  1122.                /* Could not find a place to break the line.  Ok, so
  1123.             * we will not break this.  Perhaps it is a URL.  
  1124.             * If not, it is a long word and who cares about it.
  1125.             */
  1126.                while (((ch = *buf) != 0)
  1127.                   && (ch != ' ') && (ch != '\t'))
  1128.              buf++;
  1129.  
  1130.                if (ch == 0)
  1131.              continue;
  1132.             }
  1133.                
  1134.           /* Start wrapped lines with a space.  To do this, I will
  1135.            * _temporally_ modify the previous character for the purpose
  1136.            * of creating the new space.
  1137.            */
  1138.           buf--;
  1139.           ch = *buf;
  1140.           *buf = ' ';
  1141.           
  1142.           new_l = (Slrn_Article_Line_Type *) slrn_malloc (sizeof (Slrn_Article_Line_Type), 1, 1);
  1143.           if (new_l == NULL)
  1144.             return;
  1145.           
  1146.           if (NULL == (new_l->buf = slrn_strmalloc ((char *)buf, 1)))
  1147.             {
  1148.                SLFREE (new_l);
  1149.                return;
  1150.             }
  1151.  
  1152.           *buf++ = ch;
  1153.           *buf = 0;
  1154.           
  1155.           new_l->next = l->next;
  1156.           new_l->prev = l;
  1157.           l->next = new_l;
  1158.           if (new_l->next != NULL) new_l->next->prev = new_l;
  1159.  
  1160.           new_l->flags = l->flags | WRAPPED_LINE;
  1161.           l = new_l;
  1162.           buf = (unsigned char *) new_l->buf;
  1163.           len = 0;
  1164.           Article_Wrapped = 1;
  1165.            }
  1166.          else buf++;
  1167.          
  1168.          ch = *buf;
  1169.       }
  1170.     l = l->next;
  1171.      }
  1172.    if (Quotes_Hidden) hide_quotes ();
  1173.    Slrn_Full_Screen_Update = 1;
  1174.    find_article_line_num ();
  1175. }
  1176.  
  1177. /*}}}*/
  1178.  
  1179. static void toggle_wrap_article (void)
  1180. {
  1181.    if (Article_Wrapped)
  1182.      unwrap_article ();
  1183.    else
  1184.      {
  1185.     if (Slrn_Prefix_Arg_Ptr != NULL) Slrn_Wrap_Mode = 0x7F;
  1186.     Slrn_Prefix_Arg_Ptr = NULL;
  1187.     wrap_article ();
  1188.      }
  1189. }
  1190.  
  1191.  
  1192. /*}}}*/
  1193.  
  1194. Slrn_Article_Line_Type *slrn_search_article (char *string, /*{{{*/
  1195.                          char **ptrp,
  1196.                          int is_regexp,
  1197.                          int set_current_flag)
  1198. {
  1199.    SLsearch_Type st;
  1200.    Slrn_Article_Line_Type *l;
  1201.    char *ptr;
  1202.    int ret;
  1203.    SLRegexp_Type *re = NULL;
  1204.    
  1205.    if (-1 == (ret = select_article (1)))
  1206.      return NULL;
  1207.    
  1208.    if (Article_Current_Line == NULL)
  1209.      return NULL;
  1210.    
  1211.    if (is_regexp)
  1212.      {
  1213.     re = slrn_compile_regexp_pattern (string);
  1214.     if (re == NULL)
  1215.       return NULL;
  1216.      }
  1217.    else SLsearch_init (string, 1, 0, &st);
  1218.  
  1219.    l = Article_Current_Line;
  1220.    
  1221.    if (ret == 1)
  1222.      l = l->next;
  1223.    
  1224.    while (l != NULL)
  1225.      {
  1226.     if ((l->flags & HIDDEN_LINE) == 0)
  1227.       {
  1228.          if (is_regexp)
  1229.            ptr = (char *) slrn_regexp_match (re, l->buf);
  1230.          else
  1231.            ptr = (char *) SLsearch ((unsigned char *) l->buf,
  1232.                     (unsigned char *) l->buf + strlen (l->buf),
  1233.                     &st);
  1234.          
  1235.          if (ptr != NULL)
  1236.            {
  1237.           if (ptrp != NULL) *ptrp = ptr;
  1238.           if (set_current_flag)
  1239.             {
  1240.                Article_Current_Line = l;
  1241.                find_article_line_num ();
  1242.             }
  1243.           break;
  1244.            }
  1245.       }
  1246.     l = l->next;
  1247.      }
  1248.       
  1249.    return l;
  1250. }
  1251.  
  1252. /*}}}*/
  1253.  
  1254. static void free_argc_argv_list (unsigned int argc, char **argv)
  1255. {
  1256.    while (argc)
  1257.      {
  1258.     argc--;
  1259.     slrn_free (argv[argc]);
  1260.      }
  1261. }
  1262.  
  1263.  
  1264. static char *find_url (char *l_buf, unsigned int *p_len) /*{{{*/
  1265. {
  1266.    char *ptr, *tmp, ch;
  1267.    
  1268.    while (NULL != (ptr = slrn_strchr (l_buf, ':')))
  1269.      {
  1270.     int again;
  1271.     
  1272.     if ((ptr == l_buf) || (ptr[1] != '/') || (ptr[2] != '/'))
  1273.       {
  1274.          l_buf = ptr + 1;
  1275.          continue;
  1276.       }
  1277.  
  1278.     tmp = ptr;
  1279.     
  1280.     while ((ptr > l_buf) 
  1281.            && isalpha((unsigned char)(*(ptr - 1))))
  1282.       ptr--;
  1283.  
  1284.     /* all registered and reserved scheme names are >= 3 chars long */
  1285.     if (ptr + 3 > tmp)
  1286.       {
  1287.          l_buf = tmp + 3; /* skip :// */
  1288.          continue;
  1289.       }
  1290.  
  1291.     tmp = ptr;
  1292.      
  1293.     again = 1;
  1294.     while (again)
  1295.       {
  1296.          ch = *ptr;
  1297.          
  1298.          switch (ch)
  1299.            {
  1300.         case '.':
  1301.           ptr++;
  1302.           ch = *ptr;
  1303.           if ((ch == ' ') || (ch == '\t') 
  1304.               || (ch == '\n') || (ch == 0))
  1305.             {
  1306.                ptr--;
  1307.                again = 0;
  1308.             }
  1309.           break;
  1310.  
  1311.         case ' ':
  1312.         case '\t':
  1313.         case '\n':
  1314.         case '\"':
  1315.         case '}':
  1316.         case '{':
  1317.         case ')':
  1318.         case ',':
  1319.         case ';':
  1320.         case '>':
  1321.         case '<':
  1322.           
  1323.         case 0:
  1324.           again = 0;
  1325.           break;
  1326.           
  1327.         default:
  1328.           ptr++;
  1329.           break;
  1330.            }
  1331.       }
  1332.          
  1333.     l_buf = ptr;
  1334.  
  1335.     if ((*p_len = (unsigned int) (ptr - tmp)) < 7)
  1336.       continue;
  1337.  
  1338.     ptr -= 3;
  1339.  
  1340.     if ((ptr[0] == ':')
  1341.         && (ptr[1] == '/')
  1342.         && (ptr[2] == '/'))
  1343.       continue;
  1344.       
  1345.     return tmp;
  1346.      }
  1347.    
  1348.    return NULL;
  1349. }
  1350.  
  1351. /*}}}*/
  1352.  
  1353. static int extract_urls (unsigned int *argc_ptr, char **argv, unsigned int max_argc,
  1354.              unsigned int *start) /*{{{*/
  1355. {
  1356.    Slrn_Article_Line_Type *l;
  1357.    unsigned int argc;
  1358.  
  1359.    l = Slrn_Article_Lines;
  1360.    *start = 0;
  1361.    
  1362.    argc = 0;
  1363.    while (l != NULL)
  1364.      {
  1365.     char *ptr;
  1366.     unsigned int len;
  1367.  
  1368.     if (l == Article_Current_Line)
  1369.       *start = argc;
  1370.  
  1371.     ptr = l->buf;
  1372.     while (NULL != (ptr = find_url (ptr, &len)))
  1373.       {
  1374.          if (argc == max_argc)
  1375.            {
  1376.           *argc_ptr = argc;
  1377.           return 0;
  1378.            }
  1379.  
  1380.          if (NULL == (argv[argc] = slrn_strnmalloc (ptr, len, 1)))
  1381.            {
  1382.           free_argc_argv_list (argc, argv);
  1383.           return -1;
  1384.            }
  1385.          argc++;
  1386.          ptr += len;
  1387.       }
  1388.     
  1389.     l = l->next;
  1390.      }
  1391.    
  1392.    *argc_ptr = argc;
  1393.    return 0;
  1394. }
  1395.  
  1396. /*}}}*/
  1397.  
  1398. static void launch_url (char *url) /*{{{*/
  1399. {
  1400.    char command [4096];
  1401.    char *browser, *has_percent;
  1402.    int reinit;
  1403.  
  1404.    /* add code to allow slrn to handle news: and nntp: URLs here */
  1405.  
  1406.    reinit = 0;
  1407.    if ((NULL == getenv ("DISPLAY")) 
  1408.        || (NULL == (browser = slrn_skip_whitespace (Slrn_X_Browser)))
  1409.        || (browser [0] == 0))
  1410.      {
  1411.     reinit = 1;
  1412.     browser = Slrn_NonX_Browser;
  1413.      }
  1414.  
  1415.    if (browser == NULL)
  1416.      {
  1417.     slrn_error ("No Web Browser has been defined.");
  1418.     return;
  1419.      }
  1420.  
  1421.    if (reinit == 0)               /* ==> X_Browser != NULL */
  1422.      {
  1423.     /* Non_X and X browsers may be same. */
  1424.     if ((Slrn_NonX_Browser != NULL) 
  1425.         && (0 == strcmp (Slrn_NonX_Browser, Slrn_X_Browser)))
  1426.       reinit = 1;
  1427.      }
  1428.    
  1429.     
  1430.    
  1431.    /* Perform a simple-minded syntax check. */
  1432.    has_percent = slrn_strchr (browser, '%');
  1433.    if (has_percent != NULL)
  1434.      {
  1435.     if ((has_percent[1] != 's') 
  1436.         || ((has_percent != browser) && (*(has_percent - 1) == '\\')))
  1437.       has_percent = NULL;
  1438.      }
  1439.    
  1440.    if (has_percent != NULL)
  1441.      sprintf (command, browser, url);
  1442.    else
  1443.      /* Is this quoting ok on VMS and OS/2?? */
  1444.      sprintf (command, "%s '%s'", browser, url);   
  1445.  
  1446.    slrn_posix_system (command, reinit);
  1447. }
  1448.  
  1449. static void browse_url (void) /*{{{*/
  1450. {
  1451.    char url[256];
  1452.    int selected;
  1453.    unsigned int argc, start_argc;
  1454. #define MAX_URLS 1024
  1455.    char *argv[MAX_URLS];
  1456.    int want_edit;
  1457.  
  1458.    if (-1 == extract_urls (&argc, argv, MAX_URLS, &start_argc))
  1459.      return;
  1460.    
  1461.    if (0 == argc)
  1462.      {
  1463.     slrn_error ("No URLs found.");
  1464.     return;
  1465.      }
  1466.    
  1467.    selected = 0;
  1468.    want_edit = 1;
  1469.    
  1470.    if (argc > 1)
  1471.      selected = slrn_select_list_mode ("URL", argc, argv, start_argc, &want_edit);
  1472.  
  1473.    if (-1 == selected)
  1474.      {
  1475.     free_argc_argv_list (argc, argv);
  1476.     return;
  1477.      }
  1478.  
  1479.    strncpy (url, argv[selected], sizeof (url));
  1480.    url[sizeof(url) - 1] = 0;
  1481.  
  1482.    free_argc_argv_list (argc, argv);
  1483.  
  1484.    if (want_edit 
  1485.        && (slrn_read_input ("Browse (^G aborts)", NULL, url, 0, 1) <= 0))
  1486.      {
  1487.     slrn_error ("Aborted.");
  1488.     return;
  1489.      }
  1490.  
  1491.    launch_url (url);
  1492. }
  1493.  
  1494. /*}}}*/
  1495.  
  1496. static void article_search (void) /*{{{*/
  1497. {
  1498.    static char search_str[256];
  1499.    Slrn_Article_Line_Type *l;
  1500.    
  1501.    if (slrn_read_input ("Search", search_str, NULL, 0, 0) <= 0) return;
  1502.    
  1503.    l = slrn_search_article (search_str, NULL, 0, 1);
  1504.    
  1505.    if (l == NULL) slrn_error ("Not found.");
  1506. }
  1507.  
  1508. /*}}}*/
  1509.  
  1510. /*}}}*/
  1511. /*{{{ current article movement functions */
  1512.  
  1513.  
  1514. static unsigned int art_lineup_n (unsigned int n) /*{{{*/
  1515. {
  1516.    if (select_article (1) <= 0) return 0;
  1517.    
  1518.    n = SLscroll_prev_n (&Slrn_Article_Window, n);
  1519.    
  1520.    Article_Current_Line = (Slrn_Article_Line_Type *) Slrn_Article_Window.current_line;
  1521.    
  1522.    /* Force current line to be at top of window */
  1523.    Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
  1524.    Slrn_Full_Screen_Update = 1;
  1525.    return n;
  1526. }
  1527.  
  1528. /*}}}*/
  1529.  
  1530. static void art_pageup (void) /*{{{*/
  1531. {
  1532.    if (select_article (1) <= 0)
  1533.      return;
  1534.    /* Since we always require the current line to be at the top of the
  1535.     * window, SLscroll_pageup cannot be used.  Instead, do it this way:
  1536.     */
  1537.    art_lineup_n (Slrn_Article_Window.nrows - 1);
  1538. }
  1539.  
  1540. /*}}}*/
  1541.  
  1542. int slrn_get_next_pagedn_action (void)
  1543. {
  1544.    Slrn_Header_Type *h;
  1545.  
  1546.    if (Slrn_Current_Header == NULL)
  1547.      return -1;
  1548.    
  1549.    if ((Article_Visible == 0)
  1550.        || (At_End_Of_Article != Slrn_Current_Header))
  1551.      return 0;
  1552.    
  1553.    h = Slrn_Current_Header->next;
  1554.    while (h != NULL)
  1555.      {
  1556.     if (0 == (h->flags & HEADER_READ))
  1557.       return 1;
  1558.     h = h->next;
  1559.      }
  1560.    
  1561.    return 2;
  1562. }
  1563.  
  1564.    
  1565. static void art_pagedn (void) /*{{{*/
  1566. {
  1567.    unsigned char ch, ch1;
  1568.    char *msg = NULL;
  1569.    
  1570.    if (Slrn_Current_Header == NULL) return;
  1571.  
  1572. #if SLRN_HAS_SPOILERS
  1573.    if (Spoilers_Visible == Slrn_Current_Header)
  1574.      {
  1575.     show_spoilers ();
  1576.     return;
  1577.      }
  1578. #endif
  1579.    
  1580.    
  1581.    if ((Article_Visible == 0) || (At_End_Of_Article != Slrn_Current_Header))
  1582.      {
  1583.     int av = Article_Visible;
  1584.     
  1585.     At_End_Of_Article = NULL;
  1586.  
  1587.     if ((select_article (1) <= 0)
  1588.         || (av == 0))
  1589.       return;
  1590.  
  1591.     SLscroll_pagedown (&Slrn_Article_Window);
  1592.     Article_Current_Line = (Slrn_Article_Line_Type *) Slrn_Article_Window.current_line;
  1593.     /* Force current line to be at top of window */
  1594. #if 1
  1595.     Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
  1596. #endif
  1597.     Slrn_Full_Screen_Update = 1;
  1598.     return;
  1599.      }
  1600.  
  1601.    At_End_Of_Article = NULL;
  1602.  
  1603.    if (Slrn_Batch) return;
  1604.    
  1605.    if (Slrn_Current_Header->next == NULL)
  1606.      {
  1607.     if (Slrn_Query_Next_Group)
  1608.       msg = "At end of article, press %s for next group.";
  1609.      }
  1610.    else if (Slrn_Query_Next_Article)
  1611.      msg = "At end of article, press %s for next unread article.";
  1612.    
  1613.    if ((ch1 = SLang_Last_Key_Char) == 27) ch1 = ' ';
  1614.    ch = ch1;
  1615.     
  1616.    if (msg != NULL)
  1617.      {
  1618.     slrn_message_now (msg, map_char_to_string (ch));
  1619.     ch = SLang_getkey ();
  1620.      }
  1621.    
  1622.    if (ch == ch1)
  1623.      {
  1624.     At_End_Of_Article = NULL;
  1625.     if (Slrn_Current_Header->next != NULL) art_next_unread ();
  1626.     else skip_to_next_group ();
  1627.      }
  1628.    else SLang_ungetkey (ch);
  1629. }
  1630.    
  1631.  
  1632. /*}}}*/
  1633.  
  1634. static void art_lineup (void) /*{{{*/
  1635. {
  1636.    art_lineup_n (1);
  1637. }
  1638.  
  1639. /*}}}*/
  1640.  
  1641. static void art_bob (void) /*{{{*/
  1642. {
  1643.    while (0xFFFF == art_lineup_n (0xFFFF));
  1644. }
  1645.  
  1646. /*}}}*/
  1647.  
  1648.  
  1649. static unsigned int art_linedn_n (unsigned int n) /*{{{*/
  1650. {
  1651.    if (select_article (1) <= 0)
  1652.      return 0;
  1653.  
  1654.    n = SLscroll_next_n (&Slrn_Article_Window, n);
  1655.    Article_Current_Line = (Slrn_Article_Line_Type *) Slrn_Article_Window.current_line;
  1656.    
  1657.    /* Force current line to be at top of window */
  1658.    Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
  1659.    Slrn_Full_Screen_Update = 1;
  1660.    return n;
  1661. }
  1662.  
  1663. /*}}}*/
  1664.  
  1665.  
  1666. static void art_linedn (void) /*{{{*/
  1667. {
  1668.    art_linedn_n (1);
  1669. }
  1670.  
  1671. /*}}}*/
  1672.  
  1673. static void art_eob (void) /*{{{*/
  1674. {
  1675.    while (art_linedn_n (0xFFFF) > 0)
  1676.      ;
  1677.    (void) art_lineup_n (Slrn_Article_Window.nrows / 2);
  1678. }
  1679.  
  1680. /*}}}*/
  1681.  
  1682.  
  1683. /*}}}*/
  1684.  
  1685. /*{{{ Tag functions */
  1686.  
  1687. typedef struct /*{{{*/
  1688. {
  1689.    Slrn_Header_Type **headers;
  1690.    unsigned int max_len;
  1691.    unsigned int len;
  1692. }
  1693.  
  1694. /*}}}*/
  1695. Num_Tag_Type;
  1696.  
  1697. static Num_Tag_Type Num_Tag_List;
  1698.  
  1699. static void free_tag_list (void) /*{{{*/
  1700. {
  1701.    if (Num_Tag_List.headers != NULL)
  1702.      {
  1703.     SLFREE (Num_Tag_List.headers);
  1704.     Num_Tag_List.headers = NULL;
  1705.     Num_Tag_List.len = Num_Tag_List.max_len = 0;
  1706.      }
  1707. }
  1708.  
  1709. /*}}}*/
  1710.  
  1711. int slrn_goto_num_tagged_header (int *nump) /*{{{*/
  1712. {
  1713.    unsigned int num;
  1714.    
  1715.    num = (unsigned int) *nump;
  1716.    num--;
  1717.    
  1718.    if (num >= Num_Tag_List.len)
  1719.      return 0;
  1720.    
  1721.    if (Num_Tag_List.headers == NULL)
  1722.      return 0;
  1723.    
  1724.    if (-1 == slrn_goto_header (Num_Tag_List.headers[num], 0))
  1725.      return 0;
  1726.    
  1727.    Slrn_Full_Screen_Update = 1;
  1728.    return 1;
  1729. }
  1730.  
  1731. /*}}}*/
  1732.  
  1733. static void num_tag_header (void) /*{{{*/
  1734. {
  1735.    unsigned int len;
  1736.    
  1737.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  1738.    if (Num_Tag_List.headers == NULL)
  1739.      {
  1740.     Slrn_Header_Type **headers;
  1741.     unsigned int max_len = 20;
  1742.     
  1743.     headers = (Slrn_Header_Type **) slrn_malloc (max_len * sizeof (Slrn_Header_Type),
  1744.                              0, 1);
  1745.     if (headers == NULL)
  1746.       return;
  1747.  
  1748.     Num_Tag_List.max_len = max_len;
  1749.     Num_Tag_List.headers = headers;
  1750.     Num_Tag_List.len = 0;
  1751.      }
  1752.    
  1753.    if (Num_Tag_List.max_len == Num_Tag_List.len)
  1754.      {
  1755.     Slrn_Header_Type **headers = Num_Tag_List.headers;
  1756.     unsigned int max_len = Num_Tag_List.max_len + 20;
  1757.     
  1758.     headers = (Slrn_Header_Type **) slrn_realloc ((char *)headers,
  1759.                               max_len * sizeof (Slrn_Header_Type),
  1760.                               1);
  1761.     if (headers == NULL)
  1762.       return;
  1763.     
  1764.     Num_Tag_List.max_len = max_len;
  1765.     Num_Tag_List.headers = headers;
  1766.      }
  1767.    
  1768.    Slrn_Full_Screen_Update = 1;
  1769.    if ((Slrn_Current_Header->flags & HEADER_NTAGGED) == 0)
  1770.      {
  1771.     Num_Tag_List.headers[Num_Tag_List.len] = Slrn_Current_Header;
  1772.     Num_Tag_List.len += 1;
  1773.     Slrn_Current_Header->tag_number = Num_Tag_List.len;
  1774.     Slrn_Current_Header->flags |= HEADER_NTAGGED;
  1775.     (void) slrn_header_down_n (1, 0);
  1776.     return;
  1777.      }
  1778.    
  1779.    /* It is already tagged.  So, what do we do.  The most sensible thing to
  1780.     * do is to simply give this header the last number and renumber the others
  1781.     * that follow it.  If it is the last one, untag it.
  1782.     */
  1783.    if (Slrn_Current_Header->tag_number == Num_Tag_List.len)
  1784.      {
  1785.     Num_Tag_List.len -= 1;
  1786.     Slrn_Current_Header->tag_number = 0;
  1787.     Slrn_Current_Header->flags &= ~HEADER_NTAGGED;
  1788.     return;
  1789.      }
  1790.    
  1791.    for (len = Slrn_Current_Header->tag_number + 1; len <= Num_Tag_List.len; len++)
  1792.      {
  1793.     Slrn_Header_Type *h = Num_Tag_List.headers[len - 1];
  1794.     Num_Tag_List.headers[len - 2] = h;
  1795.     h->tag_number -= 1;
  1796.      }
  1797.    Num_Tag_List.headers[len - 2] = Slrn_Current_Header;
  1798.    Slrn_Current_Header->tag_number = len - 1;
  1799. }
  1800.  
  1801. /*}}}*/
  1802.  
  1803. static void num_untag_headers (void) /*{{{*/
  1804. {
  1805.    unsigned int len;
  1806.    for (len = 1; len <= Num_Tag_List.len; len++)
  1807.      {
  1808.     Slrn_Header_Type *h = Num_Tag_List.headers[len - 1];
  1809.     h->flags &= ~HEADER_NTAGGED;
  1810.     h->tag_number = 0;
  1811.      }
  1812.    Num_Tag_List.len = 0;
  1813.    Slrn_Full_Screen_Update = 1;
  1814. }
  1815.  
  1816. /*}}}*/
  1817.  
  1818. static void toggle_one_header_tag (Slrn_Header_Type *h) /*{{{*/
  1819. {
  1820.    if (h == NULL) return;
  1821.    if (h->flags & HEADER_TAGGED)
  1822.      {
  1823.     h->flags &= ~HEADER_TAGGED;
  1824.      }
  1825.    else h->flags |= HEADER_TAGGED;
  1826. }
  1827.  
  1828. /*}}}*/
  1829.  
  1830. static void toggle_header_tag (void) /*{{{*/
  1831. {
  1832.    if (Slrn_Prefix_Arg_Ptr != NULL)
  1833.      {
  1834.     Slrn_Header_Type *h;
  1835.     
  1836.     Slrn_Prefix_Arg_Ptr = NULL;
  1837.     h = Headers;
  1838.     while (h != NULL)
  1839.       {
  1840.          h->flags &= ~HEADER_TAGGED;
  1841.          h = h->next;
  1842.       }
  1843.     Slrn_Full_Screen_Update = 1;
  1844.     return;
  1845.      }
  1846.  
  1847.    if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
  1848.        || (Slrn_Current_Header->child == NULL)/* At top with no child */
  1849.        /* or at top with child showing */
  1850.        || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
  1851.      {
  1852.     toggle_one_header_tag (Slrn_Current_Header);
  1853.      }
  1854.    else
  1855.      {
  1856.     for_this_tree (Slrn_Current_Header, toggle_one_header_tag);
  1857.      }
  1858.    (void) slrn_header_down_n (1, 0);
  1859.    Slrn_Full_Screen_Update = 1;
  1860. }
  1861.  
  1862. /*}}}*/
  1863.  
  1864. int slrn_prev_tagged_header (void) /*{{{*/
  1865. {
  1866.    Slrn_Header_Type *h = Slrn_Current_Header;
  1867.    
  1868.    if (h == NULL) return 0;
  1869.    
  1870.    while (h->prev != NULL)
  1871.      {
  1872.     h = h->prev;
  1873.     if (h->flags & HEADER_TAGGED)
  1874.       {
  1875.          slrn_goto_header (h, 0);
  1876.          return 1;
  1877.       }
  1878.      }
  1879.    return 0;
  1880. }
  1881.  
  1882. /*}}}*/
  1883.  
  1884. int slrn_next_tagged_header (void) /*{{{*/
  1885. {
  1886.    Slrn_Header_Type *h = Slrn_Current_Header;
  1887.    
  1888.    if (h == NULL) return 0;
  1889.    
  1890.    while (h->next != NULL)
  1891.      {
  1892.     h = h->next;
  1893.     if (h->flags & HEADER_TAGGED)
  1894.       {
  1895.          slrn_goto_header (h, 0);
  1896.          return 1;
  1897.       }
  1898.      }
  1899.    return 0;
  1900. }
  1901.  
  1902. /*}}}*/
  1903.  
  1904. /*}}}*/
  1905. /*{{{ Header specific functions */
  1906.  
  1907. static void find_header_line_num (void) /*{{{*/
  1908. {
  1909.    Slrn_Full_Screen_Update = 1;
  1910.    find_non_hidden_header ();
  1911.    Slrn_Header_Window.lines = (SLscroll_Type *) Headers;
  1912.    Slrn_Header_Window.current_line = (SLscroll_Type *) Slrn_Current_Header;
  1913.    SLscroll_find_line_num (&Slrn_Header_Window);
  1914. }
  1915.  
  1916. /*}}}*/
  1917.  
  1918. static void init_header_window_struct (void) /*{{{*/
  1919. {
  1920.    Slrn_Header_Window.nrows = 0;
  1921.    Slrn_Header_Window.hidden_mask = HEADER_HIDDEN;
  1922.    Slrn_Header_Window.current_line = (SLscroll_Type *) Slrn_Current_Header;
  1923.    
  1924.    Slrn_Header_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
  1925.    Slrn_Header_Window.border = 1;
  1926.    
  1927.    if (Slrn_Scroll_By_Page)
  1928.      {
  1929.     /* Slrn_Header_Window.border = 0; */
  1930.     Slrn_Header_Window.cannot_scroll = 2;
  1931.      }
  1932.  
  1933.    Slrn_Header_Window.lines = (SLscroll_Type *) Headers;
  1934.    art_winch ();               /* get row information correct */
  1935.    
  1936.    find_header_line_num ();
  1937. }
  1938.  
  1939. /*}}}*/
  1940.  
  1941. static Slrn_Header_Type *find_header_from_serverid (int id) /*{{{*/
  1942. {
  1943.    Slrn_Header_Type *h;
  1944.    
  1945.    h = Slrn_First_Header;
  1946.    while (h != NULL)
  1947.      {
  1948.     if (h->number > id) return NULL;
  1949.     if (h->number == id) break;
  1950.     h = h->real_next;
  1951.      }
  1952.    return h;
  1953. }
  1954.  
  1955. /*}}}*/
  1956.  
  1957. static void kill_cross_references (Slrn_Header_Type *h) /*{{{*/
  1958. {
  1959.    char *b;
  1960.    char group[256], *g;
  1961.    long num;
  1962.    
  1963.    if ((h->xref == NULL) || (*h->xref == 0))
  1964.      {
  1965.     if ((Header_Showing != h)
  1966.         || (NULL == (b = slrn_extract_header ("Xref: ", 6))))
  1967.       {
  1968.          return;
  1969.       }
  1970.      }
  1971.    else b = h->xref;
  1972.    
  1973.    
  1974.    /* The format appears to be:
  1975.     * Xref: machine group:num group:num...
  1976.     */
  1977.    
  1978.    /* skip machine name */
  1979.    while (*b > ' ') b++;
  1980.    
  1981.    while (*b != 0)
  1982.      {
  1983.     while (*b == ' ') b++;
  1984.     if (*b == 0) break;
  1985.     
  1986.     /* now we are looking at the groupname */
  1987.     g = group;
  1988.     while (*b && (*b != ':')) *g++ = *b++;
  1989.     if (*b++ == 0) break;
  1990.     *g = 0;
  1991.     num = atoi (b);
  1992.     while ((*b <= '9') && (*b >= '0')) b++;
  1993.     if ((num != h->number)
  1994.         || strcmp (group, Slrn_Current_Group_Name))
  1995.       slrn_mark_article_as_read (group, num);
  1996.      }
  1997. }
  1998.  
  1999. /*}}}*/
  2000.  
  2001. static void for_all_headers (void (*func)(Slrn_Header_Type *), int all) /*{{{*/
  2002. {
  2003.    Slrn_Header_Type *h, *end;
  2004.    
  2005.    Slrn_Full_Screen_Update = 1;
  2006.  
  2007.    if (func == NULL) return;
  2008.    
  2009.    if (all) end = NULL; else end = Slrn_Current_Header;
  2010.    
  2011.    h = Headers;
  2012.    
  2013.    while (h != end)
  2014.      {
  2015.     (*func)(h);
  2016.     h = h->next;
  2017.      }
  2018. }
  2019.  
  2020. /*}}}*/
  2021.  
  2022. int slrn_goto_header (Slrn_Header_Type *header, int read_flag) /*{{{*/
  2023. {
  2024.    Slrn_Header_Type *h = Slrn_First_Header;
  2025.    
  2026.    while ((h != NULL) && (h != header))
  2027.      h = h->real_next;
  2028.  
  2029.    if (h == NULL) return -1;
  2030.    
  2031.    Slrn_Current_Header = h;
  2032.    if (h->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (h, 0);
  2033.    find_header_line_num ();
  2034.    
  2035.    if (read_flag) select_article (1);
  2036.    return 0;
  2037. }
  2038.  
  2039. /*}}}*/
  2040.  
  2041. /*{{{ parse_from  */
  2042.  
  2043. static char *parse_from (char *from) /*{{{*/
  2044. {
  2045.    static char buf[256], *b;
  2046.    char ch, ch1;
  2047.    
  2048.    /* Here I am assume the following:
  2049.     * 1.  parenthesis and double quotes are comments.
  2050.     * 2.  If a name is in <> it is given precedence
  2051.     */
  2052.    
  2053.    if (from == NULL) return NULL;
  2054.    from = slrn_skip_whitespace (from);
  2055.    
  2056.    b = buf;
  2057.    
  2058.    /* strip comments */
  2059.    while ((ch = *from++) != 0)
  2060.      {
  2061.     if (ch == '"')
  2062.       {
  2063.          ch1 = ch;
  2064.          while (((ch = *from++) != 0) && (ch != ch1)) 
  2065.            ;
  2066.          if (ch == 0) return NULL;
  2067.       }
  2068.     else if (ch == '(')
  2069.       {
  2070.          int nump = 1;
  2071.          ch1 = ')';
  2072.          
  2073.          while ((ch = *from++) != 0)
  2074.            {
  2075.           if (ch == '(') nump++;
  2076.           else if (ch == ch1)
  2077.             {
  2078.                nump--;
  2079.                if (nump == 0) break;
  2080.             }
  2081.            }
  2082.          if (ch == 0) return NULL;
  2083.       }
  2084.     else if (ch == ')') return NULL;
  2085.     else if (ch != ' ') *b++ = ch;
  2086.      }
  2087.    *b = 0;
  2088.    
  2089.    from = b = buf;
  2090.    
  2091.    while ((ch = *from++) != 0)
  2092.      {
  2093.     if (ch == '<')
  2094.       {
  2095.          while (((ch = *from++) != 0) && (ch != '>'))
  2096.            {
  2097.           *b++ = ch;
  2098.            }
  2099.          if (ch == 0) return NULL;
  2100.          *b = 0;
  2101.          break;
  2102.       }
  2103.      }
  2104.    return buf;
  2105. }
  2106.  
  2107. /*}}}*/
  2108.  
  2109.  
  2110. /*}}}*/
  2111.  
  2112. /*{{{ fixup_realname */
  2113. static void fixup_realname (Slrn_Header_Type *h) /*{{{*/
  2114. {
  2115.    char *p, *pmin, *pmax;
  2116.    if ((h->realname == NULL) || (h->realname_len == 0)) return;
  2117.    pmin = h->realname;
  2118.    p = pmin + (h->realname_len - 1);
  2119.    while ((p >= pmin) && ((*p == ' ') || (*p == '\t'))) p--;
  2120.    p++;
  2121.    h->realname_len = (unsigned int) (p - pmin);
  2122.    
  2123.    /* Check if realname is enclosed in "" and if so strip them off.
  2124.     * Leave "" alone though.
  2125.     */
  2126.    if ((p > pmin + 2) &&
  2127.        (*pmin == '"') && (*(p - 1) == '"'))
  2128.      {
  2129.     h->realname++;
  2130.     h->realname_len -= 2;
  2131.      }
  2132.    
  2133.    /* Look for a character in the range [A-Za-z].  If none, found use from.
  2134.     */
  2135.    p = h->realname;
  2136.    pmax = p + h->realname_len;
  2137.    
  2138.    /* Reject a realname of root */
  2139.    if ((4 != h->realname_len)
  2140.        || (0 != strncmp (p, "root", 4)))
  2141.      {    
  2142.     while (p < pmax)
  2143.       {
  2144.          char ch = *p;
  2145.          
  2146.          if (isalpha ((unsigned char)ch)) return;
  2147.          p++;
  2148.       }
  2149.      }
  2150.  
  2151.    h->realname = parse_from (h->from);
  2152.    
  2153.    if (h->realname == NULL)
  2154.      h->realname = h->from;
  2155.    
  2156.    h->realname_len = strlen (h->realname);
  2157. }
  2158.  
  2159. /*}}}*/
  2160.  
  2161. /*}}}*/
  2162.  
  2163. /*{{{ get_header_real_name */
  2164.  
  2165. static void get_header_real_name (Slrn_Header_Type *h) /*{{{*/
  2166. {
  2167.    register char *f, *f0, *f1;
  2168.    register char ch;
  2169.    char *from = h->from;
  2170.    int n;
  2171.    
  2172.    f = from;
  2173.    while (((ch = *f) != 0) && (ch != '<') && (ch != '(')) f++;
  2174.    if (ch == '(')
  2175.      {
  2176.     /* Look for a '<'.  There is some probability that this is of the
  2177.      * form:  John Doe (800) 555-1212 <doe@nowhere.com>
  2178.      */
  2179.     f0 = f;
  2180.     f1 = f + strlen (f);
  2181.     while ((f1 > f) && (*f1 != ')') && (*f1 != '<')) f1--;
  2182.     if (*f1 == '<')
  2183.       {
  2184.          ch = '<';
  2185.          f = f1;
  2186.       }
  2187.      }
  2188.    
  2189.    if (ch != '(')
  2190.      {
  2191.     if (ch == '<')
  2192.       {
  2193.          f1 = slrn_skip_whitespace (from);
  2194.          if (*f1 == '<')
  2195.            {
  2196.           f = f1 + 1;
  2197.           from = f;
  2198.           while (((ch = *f) != 0) && (ch != '>')) f++;
  2199.            }
  2200.       }
  2201.     h->realname = from;
  2202.     h->realname_len = (unsigned int) (f - from);
  2203.     fixup_realname (h);
  2204.     return;
  2205.      }
  2206.    
  2207.    f1 = f;
  2208.    f0 = f + 1;
  2209.    f0 = slrn_skip_whitespace (f0);
  2210.    if (*f0 == ')')
  2211.      {
  2212.     h->realname = from;
  2213.     h->realname_len = (unsigned int) (f1 - from);
  2214.     fixup_realname (h);
  2215.     return;
  2216.      }
  2217.    f = f0;
  2218.    n = 0;
  2219.    while ((ch = *f) != 0)
  2220.      {
  2221.     if (ch == '(') n++;
  2222.     else if (ch == ')')
  2223.       {
  2224.          if (n == 0) break;
  2225.          n--;
  2226.       }
  2227.     f++;
  2228.      }
  2229.    h->realname = f0;
  2230.    h->realname_len = (unsigned int) (f - f0);
  2231.    fixup_realname (h);
  2232. }
  2233.  
  2234. /*}}}*/
  2235.  
  2236. /*}}}*/
  2237.  
  2238. static void extract_real_names (void) /*{{{*/
  2239. {
  2240.    Slrn_Header_Type *h = Headers;
  2241.    while (h != NULL)
  2242.      {
  2243.     get_header_real_name (h);
  2244.     h = h->next;
  2245.      }
  2246. }
  2247.  
  2248. /*}}}*/
  2249.  
  2250. static Slrn_Header_Type *find_header_from_msgid (char *r0, char *r1) /*{{{*/
  2251. {
  2252.    unsigned long hash;
  2253.    Slrn_Header_Type *h;
  2254.    unsigned int len;
  2255.    len = (unsigned int) (r1 - r0);
  2256.    hash = slrn_compute_hash ((unsigned char *) r0, (unsigned char *) r1);
  2257.    
  2258.    h = Header_Table[hash % HEADER_TABLE_SIZE];
  2259.    while (h != NULL)
  2260.      {
  2261.     if (!slrn_case_strncmp ((unsigned char *) h->msgid,
  2262.                 (unsigned char *) r0,
  2263.                 len))
  2264.       break;
  2265.     
  2266.     h = h->hash_next;
  2267.      }
  2268.    return h;
  2269. }
  2270.  
  2271. /*}}}*/
  2272.  
  2273. Slrn_Header_Type *slrn_find_header_with_msgid (char *msgid) /*{{{*/
  2274. {
  2275.    return find_header_from_msgid (msgid, msgid + strlen (msgid));
  2276. }
  2277.  
  2278. /*}}}*/
  2279.  
  2280. static void goto_article (void) /*{{{*/
  2281. {
  2282.    Slrn_Header_Type *h;
  2283.    int want_n;
  2284.    
  2285.    if (-1 == slrn_read_integer ("Goto article", NULL, &want_n))
  2286.      return;
  2287.    
  2288.    h = Headers;
  2289.    while (h != NULL)
  2290.      {
  2291.     if (h->number == want_n)
  2292.       {
  2293.          Slrn_Current_Header = h;
  2294.          if (h->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (h, 0);
  2295.          find_header_line_num ();
  2296.          return;
  2297.       }
  2298.     
  2299.     h = h->next;
  2300.      }
  2301.    
  2302.    slrn_error ("Article not found.");
  2303. }
  2304.  
  2305. /*}}}*/
  2306.  
  2307. int slrn_is_article_visible (void)
  2308. {
  2309.    int mask = 0;
  2310.    
  2311.    if ((Slrn_Current_Header != NULL)
  2312.        && Article_Visible)
  2313.      {
  2314.     if (Slrn_Current_Header == Header_Showing)
  2315.       mask = 3;
  2316.     else
  2317.       mask = 1;
  2318.      }
  2319.    
  2320.    return mask;
  2321. }
  2322.  
  2323. /*}}}*/
  2324.  
  2325. static int unfold_art_header_lines (void)
  2326. {
  2327.    Slrn_Article_Line_Type *l;
  2328.    char ch;
  2329.    
  2330.    l = Slrn_Article_Lines;
  2331.    if (l == NULL) return 0;
  2332.    l = l->next;
  2333.    
  2334.    while ((l != NULL) && (0 != (ch = l->buf[0])))
  2335.      {
  2336.     if ((ch == ' ') || (ch == '\t'))
  2337.       {
  2338.          unsigned int len0, len1;
  2339.          Slrn_Article_Line_Type *prev;
  2340.          char *new_buf;
  2341.          
  2342.          l->buf[0] = ' ';
  2343.          
  2344.          prev = l->prev;
  2345.          
  2346.          len0 = strlen (prev->buf);
  2347.          len1 = len0 + strlen (l->buf) + 1;
  2348.          
  2349.          new_buf = slrn_realloc (prev->buf, len1, 1);
  2350.          if (new_buf == NULL)
  2351.            return -1;
  2352.  
  2353.          prev->buf = new_buf;
  2354.          
  2355.          strcpy (new_buf + len0, l->buf);
  2356.          prev->next = l->next;
  2357.          if (l->next != NULL) l->next->prev = prev;
  2358.          SLFREE (l->buf);
  2359.          SLFREE (l);
  2360.          l = prev;
  2361.       }
  2362.     
  2363.     l = l->next;
  2364.      }
  2365.    return 0;
  2366. }
  2367.  
  2368.     
  2369. static int read_article (Slrn_Header_Type *h, int kill_refs, int do_mime) /*{{{*/
  2370. {
  2371.    Slrn_Article_Line_Type *l;
  2372.    unsigned int total_lines;
  2373.    char buf[NNTP_BUFFER_SIZE], *b, *b1, ch;
  2374.    unsigned int len;
  2375.    int n = h->number;
  2376.    Slrn_Header_Type *last_header_showing;
  2377.    int num_lines_update;
  2378.    int status;
  2379.  
  2380.    last_header_showing = Header_Showing;
  2381.    
  2382.    if (Header_Showing == h)
  2383.      {
  2384. #if SLRN_HAS_MIME
  2385.     if (Slrn_Use_Mime == 0)
  2386.       return 0;
  2387.     if ((do_mime && Slrn_Mime_Was_Parsed)
  2388.         || ((do_mime == 0) && (Slrn_Mime_Was_Modified == 0)))
  2389. #endif
  2390.       return 0; /* The cached copy is good */
  2391.      }
  2392.    
  2393.    if (h->tag_number) slrn_message_now ("#%2d/%-2d: Retrieving... %s", 
  2394.                     h->tag_number, Num_Tag_List.len, 
  2395.                     h->subject);
  2396.    else slrn_message_now ("[%d]Reading...", h->number);
  2397.  
  2398.    status = Slrn_Server_Obj->sv_select_article (n, h->msgid);
  2399.    if (status != OK_ARTICLE)
  2400.      {
  2401.     if (status == -1)
  2402.       {
  2403.          slrn_error ("Server failed to return article.");
  2404.          return -1;
  2405.       }
  2406.  
  2407.     slrn_error ("Article %d unavailable.", n);
  2408.     
  2409.     if (kill_refs && ((h->flags & HEADER_READ) == 0))
  2410.       {
  2411.          kill_cross_references (h);
  2412.          h->flags |= HEADER_READ;
  2413.       }
  2414.     return -1;
  2415.      }
  2416.    
  2417.    
  2418.    At_End_Of_Article = NULL;
  2419.    
  2420.    Do_Rot13 = 0;
  2421.    Article_Window_HScroll = 0;
  2422.    Slrn_Full_Screen_Update = 1;
  2423.    
  2424.    free_article ();
  2425.    
  2426.    if ((num_lines_update = Slrn_Reads_Per_Update) < 5)
  2427.      {
  2428.     if (h->lines < 200)
  2429.       num_lines_update = 20;
  2430.     else 
  2431.       num_lines_update = 50;
  2432.      }
  2433.    
  2434.    total_lines = 0;
  2435.    while (Slrn_Server_Obj->sv_read_line(buf, sizeof(buf)) != NULL)
  2436.      {
  2437.     if (SLang_Error == USER_BREAK)
  2438.       {
  2439.          if (Slrn_Server_Obj->sv_reset != NULL)
  2440.            Slrn_Server_Obj->sv_reset ();
  2441.          slrn_error ("Article transfer aborted.");
  2442.          if (Slrn_Article_Lines == NULL)
  2443.            return -1;
  2444.          break;
  2445.       }
  2446.     
  2447.     total_lines++;
  2448.     if ((1 == (total_lines % num_lines_update))
  2449.         /* Just so the ratio does not confuse the reader because of the
  2450.          * header lines...
  2451.          */
  2452.         && (total_lines < (unsigned int) h->lines))
  2453.       {
  2454.          if (h->tag_number)
  2455.            slrn_message_now ("#%2d/%-2d: Read %4d/%-4d lines (%s)",
  2456.                  h->tag_number, Num_Tag_List.len,
  2457.                  total_lines, h->lines, h->subject);
  2458.          else
  2459.            slrn_message_now ("[%d]Read %d/%d lines so far...", 
  2460.                  h->number, total_lines, h->lines);
  2461.       }
  2462.     
  2463.     len = strlen (buf);
  2464.     
  2465.     l = (Slrn_Article_Line_Type *) slrn_malloc (sizeof(Slrn_Article_Line_Type),
  2466.                             1, 1);
  2467.     if ((l == NULL)
  2468.         || (NULL == (l->buf = slrn_malloc (len + 1, 0, 1))))
  2469.       {
  2470.          slrn_free ((char *) l);
  2471.          free_article ();
  2472.          return -1;
  2473.       }
  2474.     
  2475.     /* here I am going to remove _^H combinations.  Later, it will be a
  2476.      * good idea to perform the implied underlining
  2477.      */
  2478.     b1 = l->buf;
  2479.     b = buf;
  2480.     
  2481.     if ((*b == '.') && (*(b + 1) == '.')) b++;
  2482.     
  2483.     while (0 != (ch = *b++))
  2484.       {
  2485.          if ((ch == '_') && (*b == '\b'))
  2486.            {
  2487.           b++;
  2488.            }
  2489.          else *b1++ = ch;
  2490.       }
  2491.     *b1 = 0;
  2492.     
  2493.     l->next = l->prev = NULL;
  2494.     l->flags = 0;
  2495.     
  2496.     if (Slrn_Article_Lines == NULL)
  2497.       {
  2498.          Slrn_Article_Lines = l;
  2499.       }
  2500.     else
  2501.       {
  2502.          /* l->next = Article_Current_Line->next; */
  2503.          l->prev = Article_Current_Line;
  2504.          Article_Current_Line->next = l;
  2505.          
  2506.          /* if (l->next != NULL) l->next->prev = l; */
  2507.       }
  2508.     Article_Current_Line = l;
  2509.      }
  2510.    
  2511.    Article_Current_Line = Slrn_Article_Lines;
  2512.  
  2513.    if (-1 == unfold_art_header_lines ())
  2514.      {
  2515.     free_article ();
  2516.     return -1;
  2517.      }
  2518.    
  2519.    if (kill_refs && ((h->flags & HEADER_READ) == 0))
  2520.      {
  2521.     kill_cross_references (h);
  2522.     h->flags |= HEADER_READ;
  2523.      }
  2524.    
  2525.    /* Do this now so that header lines are marked.  Mime processing 
  2526.     * assumes this.
  2527.     */
  2528.    if (h == Slrn_Current_Header) hide_art_headers ();
  2529.  
  2530.    /* This must be called before any of the other functions are called because
  2531.     * they may depend upon the line number and window information.
  2532.     */
  2533.    init_article_window_struct ();
  2534.  
  2535.  
  2536. #if SLRN_HAS_MIME
  2537.    if (Slrn_Use_Mime && (h == Slrn_Current_Header))
  2538.      {
  2539.     /* Note: the mime routines assume that the article flags are valid.
  2540.      * That is, a header line is given by line->flags & HEADER_LINE
  2541.      */
  2542.     slrn_mime_article_init ();
  2543.     if (do_mime) slrn_mime_process_article ();
  2544.      }
  2545. #endif
  2546.  
  2547.    slrn_chmap_fix_body ();
  2548.    
  2549.    if (last_header_showing != h)
  2550.      {
  2551.     Last_Read_Header = last_header_showing;
  2552.      }
  2553.    Header_Showing = h;
  2554.  
  2555.    if (h == Slrn_Current_Header)
  2556.      {
  2557. #if SLRN_HAS_SPOILERS
  2558.     if (Slrn_Spoiler_Char) mark_spoilers ();
  2559. #endif
  2560.     mark_quotes ();
  2561.        /* mark_signature unmarks lines in the signature which look like
  2562.     * quotes, so do it after mark_quotes, but before hide_quotes */
  2563.     mark_signature ();
  2564.     hide_quotes ();
  2565.       
  2566.     if (Slrn_Wrap_Mode & 0x4) wrap_article ();
  2567.  
  2568.     if (Last_Read_Header == NULL)
  2569.       Last_Read_Header = h;
  2570.      }
  2571.    
  2572.    /* slrn_set_suspension (0); */
  2573.    return 0;
  2574. }
  2575.  
  2576. /*}}}*/
  2577.  
  2578. /*{{{ reply, reply_cmd, forward, followup */
  2579.  
  2580. int slrn_insert_followup_format (char *f, FILE *fp) /*{{{*/
  2581. {
  2582.    char ch, *s, *smax;
  2583.    
  2584.    if (f == NULL)
  2585.      return 0;
  2586.  
  2587.    if (Slrn_Current_Header == NULL)
  2588.      return 0;
  2589.  
  2590.    while ((ch = *f++) != 0)
  2591.      {
  2592.     if (ch != '%')
  2593.       {
  2594.          putc (ch, fp);
  2595.          continue;
  2596.       }
  2597.     s = smax = NULL;
  2598.     ch = *f++;
  2599.     if (ch == 0) break;
  2600.     
  2601.     switch (ch)
  2602.       {
  2603.        case 's':
  2604.          s = Slrn_Current_Header->subject;
  2605.          break;
  2606.        case 'm':
  2607.          s = Slrn_Current_Header->msgid;
  2608.          break;
  2609.        case 'r':
  2610.          s = Slrn_Current_Header->realname;
  2611.          smax = s + Slrn_Current_Header->realname_len;
  2612.          break;
  2613.        case 'f':
  2614.          s = parse_from (Slrn_Current_Header->from);
  2615.          break;
  2616.        case 'n':
  2617. #if 0
  2618.          s = slrn_extract_header ("Newsgroups: ", 12);
  2619. #else
  2620.          s = Slrn_Current_Group_Name;
  2621. #endif
  2622.          break;
  2623.        case 'd':
  2624.          s = Slrn_Current_Header->date;
  2625.          break;
  2626.        case '%':
  2627.        default:
  2628.          putc (ch, fp);
  2629.       }
  2630.     
  2631.     if (s == NULL) continue;
  2632.     if (smax == NULL) fputs (s, fp);
  2633.     else fwrite (s, 1, (unsigned int) (smax - s), fp);
  2634.      }
  2635.    return 0;
  2636. }
  2637.  
  2638. /*}}}*/
  2639.  
  2640. static char *parse_reply_address (void)
  2641. {
  2642.    char *from;
  2643.    
  2644.    if (NULL == (from = slrn_extract_header ("Reply-To: ", 10)))
  2645.      from = slrn_extract_header ("From: ", 6);
  2646.    
  2647.    return parse_from (from);
  2648. }
  2649.  
  2650. /* If from != NULL, it's taken as the address to send the reply to, otherwise
  2651.  * the reply address is taken from Reply-To: or From: */
  2652. static void reply (char *from) /*{{{*/
  2653. {
  2654.    char *msgid, *subject, *f;
  2655.    Slrn_Article_Line_Type *l;
  2656.    FILE *fp;
  2657.    char file[256];
  2658.    unsigned int n;
  2659.  
  2660.    if (-1 == slrn_check_batch ())
  2661.      return;
  2662.    
  2663.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  2664.    
  2665.    if (read_article (Slrn_Current_Header, Slrn_Del_Article_Upon_Read, 1) < 0) return;
  2666.  
  2667. #if SLRN_HAS_SLANG
  2668.    SLang_run_hooks ("reply_hook", 0);
  2669.    if (SLang_Error)
  2670.      return;
  2671. #endif
  2672.    
  2673.    /* Check for FQDN.  If it appear bogus, warn user */
  2674.    if (from == NULL) from = parse_reply_address ();
  2675.    
  2676.    if ((from == NULL) 
  2677.        || (NULL == (f = slrn_strchr (from, '@')))
  2678.        || (f == from)
  2679.        || (0 == slrn_is_fqdn (f + 1)))
  2680.      {
  2681.     if (0 == slrn_get_yesno (1, "%s appears invalid.  Continue anyway",
  2682.                  ((from == NULL) ? "Email address" : from)))
  2683.       return;
  2684.      }
  2685.    
  2686.    
  2687.    if (Slrn_Use_Tmpdir)
  2688.      fp = slrn_open_tmpfile (file, "w");
  2689.    else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file, 0);
  2690.    
  2691.    if (NULL == fp)
  2692.      {
  2693.     slrn_error ("Unable to open %s for writing.", file);
  2694.     return;
  2695.      }
  2696.    
  2697.    /* parse header */
  2698.    msgid = slrn_extract_header ("Message-ID: ", 12);
  2699.    subject = slrn_extract_header ("Subject: ", 9);
  2700.  
  2701.    if (subject == NULL) subject = "";
  2702.    subject = slrn_skip_whitespace (subject);
  2703.    if (0 == slrn_case_strncmp ((unsigned char *)"Re:", (unsigned char *)subject, 3))
  2704.      subject = slrn_skip_whitespace (subject + 3);
  2705.  
  2706.    n = 0;
  2707.    fprintf (fp, "To: %s\nSubject: Re: %s\nIn-Reply-To: %s\n",
  2708.         (from == NULL ? "" : from),
  2709.         subject,
  2710.         (msgid == NULL ? "" : msgid));
  2711.    n += 3;
  2712.    
  2713. #if SLRN_GEN_FROM_EMAIL_HEADER
  2714.    fprintf (fp, "From: %s\n", slrn_make_from_string ());
  2715.    n += 1;
  2716. #endif
  2717.    
  2718.    if (0 != *Slrn_User_Info.replyto)
  2719.      {
  2720.     fprintf (fp, "Reply-To: %s\n", Slrn_User_Info.replyto);
  2721.     n += 1;
  2722.      }
  2723.    
  2724.    n += slrn_add_custom_headers (fp, Slrn_Reply_Custom_Headers, slrn_insert_followup_format);
  2725.    fputs ("\n", fp);
  2726.    
  2727.    slrn_insert_followup_format (Slrn_User_Info.reply_string, fp);
  2728.    fputs ("\n", fp);
  2729.  
  2730.    n += 2;
  2731.  
  2732.    l = Slrn_Article_Lines;
  2733.    if (Slrn_Prefix_Arg_Ptr == NULL)
  2734.      {
  2735.     while ((l != NULL) && (*l->buf != 0)) l = l->next;
  2736.     if (l != NULL) l = l->next;
  2737.      }
  2738.    
  2739.    while (l != NULL)
  2740.      {
  2741.     fprintf (fp, "%s%s\n", 
  2742.          ((Slrn_Quote_String == NULL) ? ">" : Slrn_Quote_String), 
  2743.          l->buf);
  2744.     l = l->next;
  2745.      }
  2746.    slrn_add_signature (fp);
  2747.    slrn_fclose (fp);
  2748.    
  2749.    slrn_mail_file (file, 1, n, from, subject);
  2750.    if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
  2751. }
  2752.  
  2753. /*}}}*/
  2754.  
  2755. static void reply_cmd (void) /*{{{*/
  2756. {
  2757.    if (-1 == slrn_check_batch ())
  2758.      return;
  2759.  
  2760.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  2761.    
  2762.    if (read_article (Slrn_Current_Header, Slrn_Del_Article_Upon_Read, 1) < 0) return;
  2763.    
  2764.    if (Slrn_User_Wants_Confirmation
  2765.        && (slrn_get_yesno_cancel ("Are you sure you want to reply") <= 0))
  2766.      return;
  2767.    reply (NULL);
  2768. }
  2769.  
  2770. /*}}}*/
  2771.  
  2772. static void forward_article (void) /*{{{*/
  2773. {
  2774.    char *subject;
  2775.    Slrn_Article_Line_Type *l;
  2776.    FILE *fp;
  2777.    char file[256];
  2778.    char to[256];
  2779.    int edit;
  2780.    
  2781.    if (-1 == slrn_check_batch ())
  2782.      return;
  2783.  
  2784.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  2785.    if (read_article (Slrn_Current_Header, Slrn_Del_Article_Upon_Read, 1) < 0) return;
  2786.    
  2787. #if SLRN_HAS_SLANG
  2788.    SLang_run_hooks ("forward_hook", 0);
  2789.    if (SLang_Error)
  2790.      return;
  2791. #endif
  2792.    
  2793.    *to = 0;
  2794.    if (slrn_read_input ("Forward to (^G aborts)", NULL, to, 1, 0) <= 0)
  2795.      {
  2796.     slrn_error ("Aborted.  An email address is required.");
  2797.     return;
  2798.      }
  2799.    
  2800.    if (-1 == (edit = slrn_get_yesno_cancel ("Edit the message before sending")))
  2801.      return;
  2802.    
  2803.  
  2804.    if (Slrn_Use_Tmpdir)
  2805.      {
  2806.     fp = slrn_open_tmpfile (file, "w");
  2807.      }
  2808.    else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file, 0);
  2809.    
  2810.    if (fp == NULL)
  2811.      {
  2812.     slrn_error ("Unable to open %s for writing.", file);
  2813.     return;
  2814.      }
  2815.    
  2816.    
  2817.    subject = slrn_extract_header ("Subject: ", 9);
  2818.    
  2819.    fprintf (fp, "To: %s\nSubject: Fwd: %s\n",
  2820.         to, subject == NULL ? "" : subject);
  2821.    
  2822. #if defined(IBMPC_SYSTEM)
  2823.    fprintf (fp, "From: %s\n", 
  2824.         slrn_make_from_string ());
  2825. #endif
  2826.    
  2827.    if (0 != *Slrn_User_Info.replyto)
  2828.      fprintf (fp, "Reply-To: %s (%s)\n", 
  2829.           Slrn_User_Info.replyto, Slrn_User_Info.realname);
  2830.    putc ('\n', fp);
  2831.    
  2832.    l = Slrn_Article_Lines;
  2833.    while (l != NULL)
  2834.      {
  2835.     fprintf (fp, "%s\n", l->buf);
  2836.     l = l->next;
  2837.      }
  2838.    slrn_fclose (fp);
  2839.       
  2840.    (void) slrn_mail_file (file, edit, 3, to, subject);
  2841.  
  2842.    if (Slrn_Use_Tmpdir) slrn_delete_file (file);
  2843. }
  2844.  
  2845. /*}}}*/
  2846.  
  2847.  
  2848.  
  2849. /* If prefix arg is 1, insert all headers.  If it is 2, insert all headers
  2850.  * but do not quote text nor attach signature.  2 is good for re-posting.
  2851.  */
  2852. static void followup (void) /*{{{*/
  2853. {
  2854.    char *msgid, *newsgroups, *subject, *xref, *quote_str, *cc_address;
  2855.    Slrn_Article_Line_Type *l;
  2856.    FILE *fp;
  2857.    char file [SLRN_MAX_PATH_LEN];
  2858.    unsigned int n;
  2859.    int prefix_arg;
  2860.    int perform_cc;
  2861. #if SLRN_HAS_SLANG
  2862.    int free_cc_string = 0;
  2863. #endif
  2864.    int strip_sig;
  2865.  
  2866.    /* The perform_cc testing is ugly.  Is there an easier way?? */
  2867.  
  2868.    if (-1 == slrn_check_batch ())
  2869.      return;
  2870.  
  2871.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  2872.    if (read_article (Slrn_Current_Header, Slrn_Del_Article_Upon_Read, 1) < 0) return;
  2873.  
  2874. #if SLRN_HAS_SLANG
  2875.    SLang_run_hooks ("followup_hook", 0);
  2876.    if (SLang_Error)
  2877.      return;
  2878. #endif
  2879.  
  2880.    if (Slrn_Prefix_Arg_Ptr != NULL)
  2881.      {
  2882.     prefix_arg = *Slrn_Prefix_Arg_Ptr;
  2883.     Slrn_Prefix_Arg_Ptr = NULL;
  2884.      }
  2885.    else prefix_arg = -1;
  2886.  
  2887.    strip_sig = ((prefix_arg == -1) && Slrn_Followup_Strip_Sig);
  2888.  
  2889.    if (Slrn_User_Wants_Confirmation
  2890.        && (slrn_get_yesno_cancel ("Are you sure you want to followup") <= 0))
  2891.      return;
  2892.  
  2893.    /* Here is the logic:
  2894.     * If followup-to contains an email address, use that as a CC.
  2895.     * If followup-to contains 'poster', use poster's email address.
  2896.     * Otherwise, check for mail-copies-to header.  If its value is 'never'
  2897.     *  do not add cc header.  If it is 'always', add it.  If neither of these,
  2898.     *  assume it is an email address and use that.
  2899.     * Otherwise, use email addresss.
  2900.     */
  2901.    perform_cc = -1;               /* Don't know */
  2902.    cc_address = NULL;
  2903.  
  2904.    if (NULL != (newsgroups = slrn_extract_header ("Followup-To: ", 13)))
  2905.      {
  2906.     newsgroups = slrn_skip_whitespace (newsgroups);
  2907.     cc_address = parse_from (newsgroups);
  2908.     if (cc_address != NULL)
  2909.       {
  2910.          int is_poster;
  2911.          is_poster = (0 == slrn_case_strcmp ((unsigned char *) cc_address,
  2912.                          (unsigned char *) "poster"));
  2913.          if (is_poster
  2914.          || (NULL != slrn_strchr (cc_address, '@')))
  2915.            /* The GNU newsgroups appear to have email addresses in the
  2916.         * Followup-To header.  Yuk.
  2917.         */
  2918.            {
  2919.           if (is_poster) cc_address = parse_reply_address ();
  2920.  
  2921.           if (slrn_get_yesno (1, "Do you want to reply to POSTER as poster prefers"))
  2922.             {
  2923.                reply (cc_address);
  2924.                return;
  2925.             }
  2926.           newsgroups = NULL;
  2927.           /* Add "Cc:" regardless of user settings */
  2928.           perform_cc = -1;
  2929.            }
  2930.       }
  2931.      }
  2932.  
  2933.    /* Some mailing lists have a Mail-Followup-To header.  But do this if there
  2934.     * is no Newsgroups header.
  2935.     */
  2936.    if (newsgroups == NULL)
  2937.      {
  2938.     char *mail_followupto;
  2939.  
  2940.     if (NULL == (newsgroups = slrn_extract_header ("Newsgroups: ", 12)))
  2941.       newsgroups = "";
  2942.     
  2943.     if ((*newsgroups == 0) 
  2944.         && (NULL != (mail_followupto = slrn_extract_header ("Mail-Followup-To: ", 18)))
  2945.         && (NULL != (mail_followupto = parse_from (mail_followupto))))
  2946.       {
  2947.          /* This looks like a mailing list.  Just reply */
  2948.          reply (mail_followupto);
  2949.          return;
  2950.       }
  2951.      }
  2952.    
  2953.    if (Slrn_Post_Obj->po_can_post == 0)
  2954.      {
  2955.     slrn_error ("Posting not allowed by server");
  2956.     return;
  2957.      }
  2958.  
  2959.    if ((newsgroups == NULL)
  2960.        /* Hmm..  I have also seen an empty Followup-To: header on a GNU
  2961.     * newsgroup.
  2962.     */
  2963.        || (*newsgroups == 0))
  2964.      {
  2965.     if (NULL == (newsgroups = slrn_extract_header ("Newsgroups: ", 12)))
  2966.       newsgroups = "";
  2967.      }
  2968.    
  2969.    if (perform_cc == -1)
  2970.      {
  2971.     if ((NULL != (cc_address = slrn_extract_header ("X-Mail-Copies-To: ", 18)))
  2972.         || (NULL != (cc_address = slrn_extract_header ("Mail-Copies-To: ", 16))))
  2973.       {
  2974.          /* Original poster has requested a certain cc-ing behaviour
  2975.           * which should override whatever default the user has set */
  2976.          cc_address = parse_from (cc_address);
  2977.          if ((cc_address == NULL) 
  2978.          || (0 == strcmp (cc_address, "never"))
  2979.          || (0 == strcmp (cc_address, "nobody")))
  2980.            {
  2981.           perform_cc = 0;
  2982.           cc_address = NULL;
  2983.            }
  2984.          else 
  2985.            {
  2986.           perform_cc = 1;
  2987.           if (0 == strcmp (cc_address, "always"))
  2988.             cc_address = NULL;
  2989.            }
  2990.       }
  2991.  
  2992.     if (prefix_arg == 2)
  2993.       perform_cc = 0;
  2994.      }
  2995.  
  2996.    if (cc_address == NULL)
  2997.      cc_address = parse_reply_address ();
  2998.  
  2999.    if ((perform_cc != 0)
  3000.        && (cc_address != NULL))
  3001.      {
  3002. #if SLRN_HAS_SLANG
  3003.     int cc_hook_status;
  3004.  
  3005.     if (-1 == (cc_hook_status = SLang_run_hooks ("cc_hook", 1, cc_address)))
  3006.       return;
  3007.     
  3008.     if (cc_hook_status == 1)
  3009.       {
  3010.          if (-1 == SLang_pop_slstring (&cc_address))
  3011.            return;
  3012.          free_cc_string = 1;
  3013.          if (*cc_address == 0)
  3014.            perform_cc = 0;
  3015.       }
  3016. #endif
  3017.     if (perform_cc)
  3018.       {
  3019.          if ((0 != (perform_cc = Slrn_Auto_CC_To_Poster))
  3020.          && (-1 == (perform_cc = slrn_get_yesno_cancel ("Cc message to poster"))))
  3021.            goto free_and_return;
  3022.       }
  3023.  
  3024.     if (perform_cc)
  3025.       {
  3026.          char *ff;
  3027.          
  3028.          if ((NULL == (ff = slrn_strchr (cc_address, '@')))
  3029.          || (ff == cc_address)
  3030.          || (0 == slrn_is_fqdn (ff + 1))
  3031.          || (strlen (ff + 1) < 5))
  3032.            {
  3033.           perform_cc = slrn_get_yesno_cancel ("%s appears invalid.  CC anyway", cc_address);
  3034.           if (perform_cc < 0)
  3035.             goto free_and_return;
  3036.            }
  3037.       }
  3038.      }
  3039.    
  3040.    msgid = slrn_extract_header ("Message-ID: ", 12);
  3041.    
  3042.    if (NULL != (subject = slrn_extract_header ("Subject: ", 9)))
  3043.      {
  3044.     subject = slrn_skip_whitespace (subject);
  3045.     if (((*subject | 0x20) == 'r')
  3046.         && ((*(subject + 1) | 0x20) == 'e')
  3047.         && (*(subject + 2) == ':'))
  3048.       {
  3049.          subject = slrn_skip_whitespace (subject + 3);
  3050.       }
  3051.      }
  3052.    else subject = "";
  3053.    
  3054.    if (Slrn_Use_Tmpdir)
  3055.      fp = slrn_open_tmpfile (file, "w");
  3056.    else fp = slrn_open_home_file (SLRN_FOLLOWUP_FILENAME, "w", file, 0);
  3057.    
  3058.    if (fp == NULL)
  3059.      {
  3060.     slrn_error ("Unable to open %s for writing.", file);
  3061.     goto free_and_return;
  3062.      }
  3063.    
  3064.    fprintf (fp, "Newsgroups: %s\nSubject: Re: %s\n",
  3065.         newsgroups, subject);  n = 3;
  3066.    
  3067.    xref = slrn_extract_header("References: ", 12);
  3068.    if (msgid != NULL)
  3069.      {
  3070.     if (xref == NULL)
  3071.       fprintf (fp, "References: %s\n", msgid);
  3072.     else 
  3073.       fprintf (fp, "References: %s %s\n", xref, msgid);
  3074.     n++;
  3075.      }
  3076.  
  3077.    if (Slrn_User_Info.org != NULL)
  3078.      {
  3079.     fprintf (fp, "Organization: %s\n", Slrn_User_Info.org);
  3080.     n++;
  3081.      }
  3082.    
  3083.    if (perform_cc
  3084.        && (cc_address != NULL))
  3085.      {
  3086.     fprintf (fp, "Cc: %s\n", cc_address);
  3087.     n++;
  3088.      }
  3089.    
  3090.    if (0 != *Slrn_User_Info.replyto)
  3091.      {
  3092.     fprintf (fp, "Reply-To: %s\n", Slrn_User_Info.replyto);
  3093.     n++;
  3094.      }
  3095.    
  3096.    fprintf (fp, "Followup-To: \n");
  3097.    n++;
  3098.    
  3099.    n += slrn_add_custom_headers (fp, Slrn_Followup_Custom_Headers, slrn_insert_followup_format);
  3100.    
  3101.    fputs ("\n", fp);
  3102.    
  3103.    if (prefix_arg != 2)
  3104.      {
  3105.     slrn_insert_followup_format (Slrn_User_Info.followup_string, fp);
  3106.     fputs ("\n", fp);
  3107.      }
  3108.    n += 1;                   /* by having + 1, the cursor will be
  3109.                     * placed on the first line of message.
  3110.                     */
  3111.  
  3112.    /* skip header */
  3113.    l = Slrn_Article_Lines;
  3114.    if (prefix_arg == -1)
  3115.      {
  3116.     while ((l != NULL) && (*l->buf != 0)) l = l->next;
  3117.     if (l != NULL) l = l->next;
  3118.      }
  3119.    
  3120.    if (prefix_arg == 2) quote_str = ""; 
  3121.    else if (NULL == (quote_str = Slrn_Quote_String))
  3122.      quote_str = ">";
  3123.  
  3124.    while (l != NULL)
  3125.      {
  3126.     if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
  3127.       unwrap_line (l->next);
  3128.     
  3129.     if (strip_sig
  3130.         && (l->flags & SIGNATURE_LINE))
  3131.       break;
  3132.  
  3133.     fprintf (fp, "%s%s\n", quote_str, l->buf);
  3134.     
  3135.     l = l->next;
  3136.      }
  3137.  
  3138.    if (prefix_arg != 2) slrn_add_signature (fp);
  3139.    slrn_fclose (fp);
  3140.    
  3141.    if (slrn_edit_file (Slrn_Editor_Post, file, n, 1) >= 0)
  3142.      {
  3143.     if (0 == slrn_post_file (file, cc_address, 0))
  3144.       {
  3145.          /* Success. */
  3146.          if (Slrn_Last_Message_Id != NULL)
  3147.            {
  3148.           /* Later I want to actually get the article from the server
  3149.            * and display it.  Of course I can only do it if I know
  3150.            * the message-id.  If Slrn_Last_Message_Id is non-null, 
  3151.            * then slrn generated the message-id and we can do it.
  3152.            */
  3153.            }
  3154.       }
  3155.      }
  3156.    
  3157.    if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
  3158.    
  3159.    free_and_return:
  3160. #if SLRN_HAS_SLANG
  3161.    if (free_cc_string && (cc_address != NULL))
  3162.      SLang_free_slstring (cc_address);
  3163. #endif
  3164. }
  3165.  
  3166. /*}}}*/
  3167.  
  3168. /* Copy a message, adding a "Supersedes: " header for the message it replaces.
  3169.  * Not all headers of original are preserved; notably Cc is discarded.
  3170.  */
  3171. static void supersede (void) /*{{{*/
  3172. {
  3173.    char *msgid, *newsgroups, *subject, *xref;
  3174.    Slrn_Article_Line_Type *l;
  3175.    FILE *fp;
  3176.    char file[SLRN_MAX_PATH_LEN];
  3177.    unsigned int n;
  3178.    char *from;
  3179.    char me[256];
  3180.  
  3181.     if (-1 == slrn_check_batch ())
  3182.       return;
  3183.  
  3184.     slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  3185.     if (read_article (Slrn_Current_Header, Slrn_Del_Article_Upon_Read, 1) < 0)
  3186.       return;
  3187.  
  3188. #if SLRN_HAS_SLANG
  3189.    SLang_run_hooks ("supersede_hook", 0);
  3190.    if (SLang_Error)
  3191.      return;
  3192. #endif
  3193.    
  3194.     if (Slrn_User_Wants_Confirmation
  3195.         && (slrn_get_yesno_cancel ("Are you sure you want to supersede") <= 0))
  3196.       return;
  3197.  
  3198.     from = slrn_extract_header ("From: ", 6);
  3199.     if (from != NULL) from = parse_from (from);
  3200.     if (from == NULL) from = "";
  3201.     sprintf (me, "%s@%s", Slrn_User_Info.username, Slrn_User_Info.hostname);
  3202.     if (slrn_case_strcmp ((unsigned char *) from, (unsigned char *) me))
  3203.       {
  3204.         slrn_error ("Failed: Your name: '%s' is not '%s'", me, from);
  3205.         return;
  3206.       }
  3207.  
  3208.    if (NULL == (newsgroups = slrn_extract_header ("Newsgroups: ", 12)))
  3209.      newsgroups = "";
  3210.    if (NULL == (subject = slrn_extract_header ("Subject: ", 9)))
  3211.      subject = "";
  3212.    if (NULL == (msgid = slrn_extract_header ("Message-ID: ", 12)))
  3213.      msgid = "";
  3214.    xref = slrn_extract_header("References: ", 12);
  3215.  
  3216.    if (Slrn_Use_Tmpdir)
  3217.      fp = slrn_open_tmpfile (file, "w");
  3218.    else fp = slrn_open_home_file (SLRN_FOLLOWUP_FILENAME, "w", file, 0);
  3219.  
  3220.    if (fp == NULL)
  3221.      {
  3222.     slrn_error ("Unable to open %s for writing.", file);
  3223.     return;
  3224.      }
  3225.  
  3226.     fprintf (fp, "Newsgroups: %s\nSubject: %s\nSupersedes: %s\n",
  3227.          newsgroups, subject, msgid);  n = 3;
  3228.     
  3229.     if (xref != NULL)
  3230.       {
  3231.          fprintf (fp, "References: %s\n", xref);
  3232.      n++;
  3233.       }
  3234.  
  3235.     if (Slrn_User_Info.org != NULL)
  3236.       {
  3237.      fprintf (fp, "Organization: %s\n", Slrn_User_Info.org);
  3238.      n++;
  3239.       }
  3240.     
  3241.     if (0 != *Slrn_User_Info.replyto)
  3242.      {
  3243.     fprintf (fp, "Reply-To: %s\n", Slrn_User_Info.replyto);
  3244.     n++;
  3245.      }
  3246.  
  3247.     fprintf (fp, "Followup-To: \n");
  3248.     n++;
  3249.     
  3250.     fputs ("\n", fp);
  3251.     n += 1;    
  3252.  
  3253.     /* skip header */
  3254.     l = Slrn_Article_Lines;
  3255.     while ((l != NULL) && (*l->buf != 0)) l = l->next;
  3256.     if (l != NULL) l = l->next;
  3257.     
  3258.     while (l != NULL)
  3259.       {
  3260.      if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
  3261.        unwrap_line (l->next);
  3262.      
  3263.      fprintf (fp, "%s\n", l->buf);
  3264.      
  3265.      l = l->next;
  3266.       }
  3267.  
  3268.     slrn_fclose (fp);
  3269.     
  3270.    if (slrn_edit_file (Slrn_Editor_Post, file, n, 1) >= 0)
  3271.      {
  3272.     if (0 == slrn_post_file (file, NULL, 0))
  3273.       {
  3274.       }
  3275.      }
  3276.    if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
  3277. }
  3278.  
  3279. /*}}}*/
  3280.  
  3281.  
  3282.  
  3283. /*}}}*/
  3284.  
  3285. /*{{{ header movement functions */
  3286.  
  3287. unsigned int slrn_header_down_n (unsigned int n, int err) /*{{{*/
  3288. {
  3289.    unsigned int m;
  3290.    
  3291.    m = SLscroll_next_n (&Slrn_Header_Window, n);
  3292.    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
  3293.    
  3294.    if (err && (m != n))
  3295.      slrn_error ("End of buffer.");
  3296.    
  3297.    return m;
  3298. }
  3299. /*}}}*/
  3300.  
  3301. static void header_down (void) /*{{{*/
  3302. {
  3303.    slrn_header_down_n (1, 1);
  3304. }
  3305.  
  3306. /*}}}*/
  3307.  
  3308. unsigned int slrn_header_up_n (unsigned int n, int err) /*{{{*/
  3309. {
  3310.    unsigned int m;
  3311.    
  3312.    m = SLscroll_prev_n (&Slrn_Header_Window, n);
  3313.    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
  3314.    
  3315.    if (err && (m != n))
  3316.      slrn_error ("Top of buffer.");
  3317.    
  3318.    return m;
  3319. }
  3320.  
  3321. /*}}}*/
  3322.  
  3323. static void header_up (void) /*{{{*/
  3324. {
  3325.    slrn_header_up_n (1, 1);
  3326. }
  3327.  
  3328. /*}}}*/
  3329.  
  3330. static void header_pageup (void) /*{{{*/
  3331. {
  3332.    Slrn_Full_Screen_Update = 1;
  3333.    if (-1 == SLscroll_pageup (&Slrn_Header_Window))
  3334.      slrn_error ("Top of buffer.");
  3335.    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
  3336. }
  3337.  
  3338. /*}}}*/
  3339.  
  3340. static void header_pagedn (void) /*{{{*/
  3341. {
  3342.    Slrn_Full_Screen_Update = 1;
  3343.    if (-1 == SLscroll_pagedown (&Slrn_Header_Window))
  3344.      slrn_error ("End of buffer.");
  3345.    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
  3346. }
  3347.  
  3348. /*}}}*/
  3349.  
  3350. static void header_bob (void) /*{{{*/
  3351. {
  3352.    Slrn_Current_Header = Headers;
  3353.    find_header_line_num ();
  3354. }
  3355.  
  3356. /*}}}*/
  3357.  
  3358. static void header_eob (void) /*{{{*/
  3359. {
  3360.    while (0xFFFF == slrn_header_down_n (0xFFFF, 0));
  3361. }
  3362.  
  3363. /*}}}*/
  3364.  
  3365. static int prev_unread (void) /*{{{*/
  3366. {
  3367.    Slrn_Header_Type *h;
  3368.    
  3369.    h = Slrn_Current_Header -> prev;
  3370.    
  3371.    while (h != NULL)
  3372.      {
  3373.     if (0 == (h->flags & HEADER_READ)) break;
  3374.     h = h->prev;
  3375.      }
  3376.    
  3377.    if (h == NULL)
  3378.      {
  3379.     slrn_message ("No previous unread articles.");
  3380.     return 0;
  3381.      }
  3382.       
  3383.    Slrn_Current_Header = h;
  3384.  
  3385.    if (h->flags & HEADER_HIDDEN)
  3386.      slrn_uncollapse_this_thread (h, 0);
  3387.  
  3388.    find_header_line_num ();
  3389.    return 1;
  3390. }
  3391.  
  3392. /*}}}*/
  3393.  
  3394. static void goto_last_read (void) /*{{{*/
  3395. {
  3396.    if (Last_Read_Header == NULL) return;
  3397.    slrn_goto_header (Last_Read_Header, 1);
  3398. }
  3399.  
  3400. /*}}}*/
  3401.  
  3402. static void art_prev_unread (void) /*{{{*/
  3403. {
  3404.    if (prev_unread () && Article_Visible) art_pagedn  ();
  3405. }
  3406.  
  3407. /*}}}*/
  3408.  
  3409. int slrn_next_unread_header (void) /*{{{*/
  3410. {
  3411.    Slrn_Header_Type *h;
  3412.    
  3413.    h = Slrn_Current_Header->next;
  3414.    
  3415.    while (h != NULL)
  3416.      {
  3417.     if (0 == (h->flags & HEADER_READ)) break;
  3418.     h = h->next;
  3419.      }
  3420.    
  3421.    if (h == NULL)
  3422.      {
  3423.     slrn_message ("No following unread articles.");
  3424.     return 0;
  3425.      }
  3426.    
  3427.    Slrn_Current_Header = h;
  3428.    if (h->flags & HEADER_HIDDEN)
  3429.      slrn_uncollapse_this_thread (h, 0);
  3430.      
  3431.    find_header_line_num ();
  3432.  
  3433.    return 1;
  3434. }
  3435.  
  3436. /*}}}*/
  3437.  
  3438. static void art_next_unread (void) /*{{{*/
  3439. {
  3440.    char ch;
  3441.    unsigned char ch1;
  3442.    
  3443.    if (slrn_next_unread_header ())
  3444.      {
  3445.     if (Article_Visible) art_pagedn  ();
  3446.     return;
  3447.      }
  3448.    
  3449.    if (Slrn_Query_Next_Group == 0)
  3450.      {
  3451.     skip_to_next_group ();
  3452.     return;
  3453.      }
  3454.    
  3455.    if (Slrn_Batch) return;
  3456.    
  3457.    ch1 = SLang_Last_Key_Char;
  3458.    if (ch1 == 27) ch1 = 'n';
  3459.    slrn_message_now ("No following unread articles.  Press %s for next group.",
  3460.              map_char_to_string (ch1));
  3461.    
  3462.    ch = SLang_getkey ();
  3463.    
  3464.    if ((unsigned char)ch == ch1)
  3465.      {
  3466.     skip_to_next_group ();
  3467.      }
  3468.    else SLang_ungetkey (ch);
  3469. }
  3470.  
  3471. /*}}}*/
  3472.  
  3473. static void next_high_score (void) /*{{{*/
  3474. {
  3475.    Slrn_Header_Type *l;
  3476.    
  3477.    l = Slrn_Current_Header->next;
  3478.    
  3479.    while (l != NULL)
  3480.      {
  3481.     if (l->flags & HEADER_HIGH_SCORE)
  3482.       {
  3483.          break;
  3484.       }
  3485.     l = l->next;
  3486.      }
  3487.    
  3488.    if (l == NULL)
  3489.      {
  3490.     slrn_error ("No more high scoring articles.");
  3491.     return;
  3492.      }
  3493.    
  3494.    if (l->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (l, 0);
  3495.    
  3496.    Slrn_Current_Header = l;
  3497.    find_header_line_num ();
  3498.    
  3499.    if (Article_Visible)
  3500.      {
  3501.     if (Header_Showing != Slrn_Current_Header) 
  3502.       art_pagedn ();
  3503.      }
  3504. }
  3505.  
  3506. /*}}}*/
  3507.  
  3508. static Slrn_Header_Type *Same_Subject_Start_Header;
  3509. static void next_header_same_subject (void) /*{{{*/
  3510. {
  3511.    SLsearch_Type st;
  3512.    Slrn_Header_Type *l;
  3513.    static char same_subject[256];
  3514.    
  3515.    if ((Same_Subject_Start_Header == NULL)
  3516.        || (Slrn_Prefix_Arg_Ptr != NULL))
  3517.      {
  3518.     Slrn_Prefix_Arg_Ptr = NULL;
  3519.     if (slrn_read_input ("Subject", same_subject, NULL, 0, 0) <= 0) return;
  3520.     Same_Subject_Start_Header = Slrn_Current_Header;
  3521.      }
  3522.    SLsearch_init (same_subject, 1, 0, &st);
  3523.    
  3524.    l = Slrn_Current_Header->next;
  3525.    
  3526.    while (l != NULL)
  3527.      {
  3528.     if (
  3529. #if 0
  3530.         /* Do we want to do this?? */
  3531.         ((l->flags & HEADER_READ) == 0) &&
  3532. #endif
  3533.         (l->subject != NULL)
  3534.         && (NULL != SLsearch ((unsigned char *) l->subject,
  3535.                   (unsigned char *) l->subject + strlen (l->subject),
  3536.                   &st)))
  3537.       break;
  3538.     
  3539.     l = l->next;
  3540.      }
  3541.    
  3542.    if (l == NULL)
  3543.      {
  3544.     slrn_error ("No more articles on that subject.");
  3545.     l = Same_Subject_Start_Header;
  3546.     Same_Subject_Start_Header = NULL;
  3547.      }
  3548.    
  3549.    if (l->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (l, 0);
  3550.    Slrn_Current_Header = l;
  3551.    find_header_line_num ();
  3552.    if ((Same_Subject_Start_Header != NULL)
  3553.        && (Article_Visible))
  3554.      {
  3555.     art_pagedn ();
  3556.      }
  3557. }
  3558.  
  3559. /*}}}*/
  3560.  
  3561. static void goto_header_number (void) /*{{{*/
  3562. {
  3563.    int diff, i, ich;
  3564.  
  3565.    if (Slrn_Batch) return;
  3566.  
  3567.    i = 0;
  3568.    ich = SLang_Last_Key_Char;
  3569.    do
  3570.      {
  3571.     i = i * 10 + (ich - '0');
  3572.     if (10 * i > Largest_Header_Number)
  3573.       {
  3574.          ich = '\r';
  3575.          break;
  3576.       }
  3577.     slrn_message_now ("Goto Header: %d", i);
  3578.      }
  3579.    while ((ich = SLang_getkey ()), (ich <= '9') && (ich >= '0'));
  3580.    
  3581.    if (SLKeyBoard_Quit) return;
  3582.    
  3583.    if (ich != '\r')
  3584.      SLang_ungetkey (ich);
  3585.    
  3586.    diff = i - Last_Cursor_Row;
  3587.    if (diff > 0) slrn_header_down_n (diff, 0); else slrn_header_up_n (-diff, 0);
  3588. #if SLRN_HAS_SLANG
  3589.    SLang_run_hooks ("header_number_hook", 0);
  3590. #endif
  3591.    Slrn_Full_Screen_Update = 1;
  3592. }
  3593.  
  3594. /*}}}*/
  3595.  
  3596. /*}}}*/
  3597.  
  3598. /*{{{ article save/decode functions */
  3599.  
  3600. static int write_article_lines (FILE *fp)
  3601. {
  3602.    Slrn_Article_Line_Type *l;
  3603.    
  3604.    l = Slrn_Article_Lines;
  3605.  
  3606.    while (l != NULL)
  3607.      {
  3608.     char *buf;
  3609.     Slrn_Article_Line_Type *next = l->next;
  3610.     
  3611.     buf = l->buf;
  3612.     if (l->flags & WRAPPED_LINE) buf++;   /* skip space */
  3613.     
  3614.     if (EOF == fputs (buf, fp))
  3615.       return -1;
  3616.     
  3617.     if ((next == NULL) || (0 == (next->flags & WRAPPED_LINE)))
  3618.       {
  3619.          if (EOF == putc ('\n', fp))
  3620.            return -1;
  3621.       }
  3622.     l = next;
  3623.      }
  3624.     
  3625.    return 0;
  3626. }
  3627.  
  3628. int slrn_save_current_article (char *file) /*{{{*/
  3629. {
  3630.    FILE *fp = NULL;
  3631.    
  3632.    if (Slrn_Current_Header == NULL) 
  3633.      return -1;
  3634.    
  3635.    if (read_article (Slrn_Current_Header, Slrn_Del_Article_Upon_Read, 0) < 0)
  3636.      return -1;
  3637.    
  3638.    fp = fopen (file, "w");
  3639.    
  3640.    if (fp == NULL)
  3641.      {
  3642.     slrn_error ("Unable to open %s.", file);
  3643.     return -1;
  3644.      }
  3645.  
  3646.    if (-1 == write_article_lines (fp))
  3647.      {
  3648.     slrn_error ("Error writing to %s.", file);
  3649.     fclose (fp);
  3650.     return -1;
  3651.      }
  3652.    
  3653.    fclose (fp);
  3654.    return 0;
  3655. }
  3656.  
  3657.  
  3658. /*}}}*/
  3659.  
  3660. static int save_article_as_unix_mail (Slrn_Header_Type *h, FILE *fp) /*{{{*/
  3661. {
  3662.    char *from;
  3663.    time_t now;
  3664.    Slrn_Article_Line_Type *l;
  3665.    int is_wrapped;
  3666.  
  3667.    if (read_article (h, Slrn_Del_Article_Upon_Read, 0) < 0) return -1;
  3668.    
  3669.    is_wrapped = Article_Wrapped;
  3670.    if (is_wrapped) unwrap_article ();
  3671.  
  3672.    from = slrn_extract_header ("From: ", 6);
  3673.    if (from != NULL) from = parse_from (from);
  3674.    if ((from == NULL) || (*from == 0)) from = "nobody@nowhere";
  3675.    
  3676.    time (&now);
  3677.    fprintf (fp, "From %s %s", from, ctime(&now));
  3678.    
  3679.    l = Slrn_Article_Lines;
  3680.    while (l != NULL)
  3681.      {
  3682.     if ((*l->buf == 'F')
  3683.         && !strncmp ("From", l->buf, 4)
  3684.         && ((unsigned char)(l->buf[4]) <= ' '))
  3685.       {
  3686.          putc ('>', fp);
  3687.       }
  3688.     
  3689.     fputs (l->buf, fp);
  3690.     putc ('\n', fp);
  3691.     l = l->next;
  3692.      }
  3693.    fputs ("\n\n", fp);
  3694.    
  3695.    if (is_wrapped) wrap_article ();
  3696.  
  3697.    return 0;
  3698. }
  3699.  
  3700. /*}}}*/
  3701.  
  3702. static char *save_article_to_file (char *defdir, char *input_string) /*{{{*/
  3703. {
  3704.    char file[256];
  3705.    char name[256];
  3706.    int save_tagged = 0;
  3707.    int save_thread = 0;
  3708.    int save_simple;
  3709.    FILE *fp;
  3710.    
  3711.    if (-1 == slrn_check_batch ())
  3712.      return NULL;
  3713.    
  3714.    if (Num_Tag_List.len)
  3715.      {
  3716.     save_tagged = slrn_get_yesno_cancel ("Save tagged articles");
  3717.     if (save_tagged < 0) return NULL;
  3718.      }
  3719.    else if ((Slrn_Current_Header->child != NULL)
  3720.         && (Slrn_Current_Header->child->flags & HEADER_HIDDEN))
  3721.      {
  3722.     save_thread = slrn_get_yesno_cancel ("Save this thread");
  3723.     if (save_thread == -1) return NULL;
  3724.      }
  3725.    
  3726.    save_simple = !(save_tagged || save_thread);
  3727.    
  3728.    if (*Output_Filename == 0)
  3729.      {
  3730. #ifdef VMS 
  3731.     char *p;
  3732. #endif
  3733.     unsigned int defdir_len;
  3734.     if (defdir == NULL) defdir = "News";
  3735.     
  3736.     defdir_len = strlen (defdir);
  3737. #ifdef VMS
  3738.     sprintf (name, "%s/", defdir);
  3739.     p = name + strlen (name);
  3740.     strcpy (p, Slrn_Current_Group_Name);
  3741.     while (*p != 0)
  3742.       {
  3743.          if (*p == '.') *p = '_';
  3744.          p++;
  3745.       }
  3746.     strcpy (p, ".txt");
  3747. #else
  3748.     sprintf (name, "%s/%s", defdir, Slrn_Current_Group_Name);
  3749. #endif
  3750.     
  3751. #if !defined(VMS) && !defined(IBMPC_SYSTEM)
  3752.     /* Lowercase first letter and see if it exists. */
  3753.     name[defdir_len + 1] = LOWER_CASE(name[defdir_len + 1]);
  3754. #endif
  3755.     slrn_make_home_filename (name, file);
  3756.  
  3757. #if !defined(VMS) && !defined(IBMPC_SYSTEM)
  3758.     if (1 != slrn_file_exists (file))
  3759.       {
  3760.          /* Lowercase version does not exist so user uppercase form. */
  3761.          name[defdir_len + 1] = UPPER_CASE(name[defdir_len + 1]);
  3762.          slrn_make_home_filename (name, file);
  3763.       }
  3764. #endif
  3765.      }
  3766.    else strcpy (file, Output_Filename);
  3767.    
  3768.    if (slrn_read_input (input_string, NULL, file, 1, 1) <= 0)
  3769.      {
  3770.     slrn_error ("Aborted.");
  3771.     return NULL;
  3772.      }
  3773.    
  3774.    if (NULL == (fp = fopen (file, "a")))
  3775.      {
  3776.     slrn_error ("Unable to open %s", file);
  3777.     return NULL;
  3778.      }
  3779.  
  3780.    strcpy (Output_Filename, file);
  3781.    if (save_simple) save_article_as_unix_mail (Slrn_Current_Header, fp);
  3782.    else if (save_tagged)
  3783.      {
  3784.     unsigned int i;
  3785.     unsigned int num_saved = 0;
  3786.  
  3787.     for (i = 0; i < Num_Tag_List.len; i++)
  3788.       {
  3789.          if (-1 == save_article_as_unix_mail (Num_Tag_List.headers[i], fp))
  3790.            {
  3791.           slrn_smg_refresh ();
  3792.           if (SLang_Error == SL_USER_BREAK)
  3793.             break;
  3794.  
  3795.           SLang_Error = 0;
  3796.           (void) SLang_input_pending (5);   /* half second delay */
  3797.           slrn_clear_message ();
  3798.            }
  3799.          else num_saved++;
  3800.       }
  3801.     if (num_saved == 0) return NULL;
  3802.      }
  3803.    else
  3804.      {
  3805.     Slrn_Header_Type *h = Slrn_Current_Header;
  3806.     unsigned int num_saved = 0;
  3807.     do
  3808.       {
  3809.          if (-1 == save_article_as_unix_mail (h, fp))
  3810.            {
  3811.           slrn_smg_refresh ();
  3812.           SLang_Error = 0;
  3813.           (void) SLang_input_pending (5);   /* half second delay */
  3814.            }
  3815.          else num_saved++;
  3816.  
  3817.          h = h->next;
  3818.       }
  3819.     while ((h != NULL) && (h->parent != NULL));
  3820.     if (num_saved == 0) return NULL;
  3821.      }
  3822.    slrn_fclose (fp);
  3823.    
  3824.    if (SLang_Error) return NULL;
  3825.    
  3826.    return Output_Filename;
  3827. }
  3828.  
  3829. /*}}}*/
  3830.  
  3831. static void save_article (void) /*{{{*/
  3832. {
  3833.    (void) save_article_to_file (Slrn_Save_Directory, "Save to file (^G aborts)");
  3834. }
  3835.  
  3836. /*}}}*/
  3837.  
  3838. #if SLRN_HAS_DECODE
  3839. #if SLRN_HAS_UUDEVIEW
  3840. static int the_uudeview_busy_callback (void *param, uuprogress *progress)
  3841. {
  3842.    char stuff[26];
  3843.    unsigned int count, count_max;
  3844.    int pcts;
  3845.    char *ptr;
  3846.    
  3847.    if (progress->action != UUACT_DECODING) 
  3848.      return 0;
  3849.    
  3850.    pcts = (int)((100 * progress->partno + progress->percent - 100) / progress->numparts);
  3851.    
  3852.    count_max = sizeof (stuff) - 1;
  3853.  
  3854.    for (count = 0; count < count_max; count++)
  3855.      stuff[count] = (count < pcts/4) ? '#' : '.';
  3856.  
  3857.    stuff [count_max] = 0;
  3858.  
  3859.    slrn_message_now ("decoding %10s (%3d/%3d) %s",
  3860.              progress->curfile,
  3861.              progress->partno, progress->numparts,
  3862.              stuff);
  3863.    return 0;
  3864. }
  3865.  
  3866. static int do_slrn_uudeview (char *uu_dir, char *file)
  3867. {
  3868.    uulist *item;
  3869.    char where [SLRN_MAX_PATH_LEN];
  3870.    int i, ret;
  3871.  
  3872.    slrn_make_home_dirname (uu_dir, where);
  3873.    /* this is expecting a '/' at the end...so we put one there */
  3874.    strcat (where, "/");
  3875.    
  3876.    ret = UUInitialize ();
  3877.    ret = UUSetBusyCallback (NULL, the_uudeview_busy_callback, 100);
  3878.    ret = UUSetOption (UUOPT_DESPERATE, 1, NULL);
  3879.    ret = UUSetOption (UUOPT_SAVEPATH, 0, where);
  3880.    if (UURET_OK != (ret = UULoadFile (file, NULL, 0)))
  3881.      {
  3882.     /* Not all systems have strerror... */
  3883.     if (ret == UURET_IOERR)
  3884.       slrn_error ("could not load %s: errno = %d", 
  3885.               file, UUGetOption (UUOPT_ERRNO, NULL, NULL, 0));
  3886.     else
  3887.       slrn_error ("could not load %s: %s",  UUstrerror (ret));
  3888.      }
  3889.    
  3890.    i = 0;
  3891.    while (NULL != (item = UUGetFileListItem (i)))
  3892.      {
  3893.     i++;
  3894.     
  3895.     if (UURET_OK != (ret = UUDecodeFile (item, NULL)))
  3896.       {
  3897.          char *f;
  3898.          char *err;
  3899.          
  3900.          if (NULL == (f = item->filename)) f = "oops";
  3901.          
  3902.          if (ret == UURET_IOERR) err = "I/O error.";
  3903.          else err = UUstrerror (ret);
  3904.          
  3905.          slrn_error ("error decoding %s: %s", f, err);
  3906.       }
  3907.      }
  3908.  
  3909.    UUCleanUp ();
  3910. }
  3911. #endif
  3912.  
  3913. static void decode_article (void) /*{{{*/
  3914. {
  3915.    char *uu_dir;
  3916.    char *file;
  3917.    
  3918.    if (NULL == (uu_dir = Slrn_Decode_Directory))
  3919.      {
  3920.     uu_dir = Slrn_Save_Directory;
  3921.      }
  3922.    else *Output_Filename = 0;           /* force it to use this directory */
  3923.  
  3924.    file = save_article_to_file(uu_dir, "Filename (^G aborts)");
  3925.  
  3926.    if (file == NULL) return;
  3927.    
  3928.    if (1 == slrn_get_yesno (1, "decode %s", file))
  3929.      {
  3930. # if SLRN_HAS_UUDEVIEW
  3931.     (void) do_slrn_uudeview (uu_dir, file);
  3932. # else
  3933.     (void) slrn_uudecode_file (file, NULL, 0, NULL);
  3934. # endif
  3935.  
  3936.     if (SLang_Error == 0)
  3937.       {
  3938.          if (1 == slrn_get_yesno (1, "Delete %s", file))
  3939.            {
  3940.           if (-1 == slrn_delete_file (file))
  3941.             slrn_error ("Unable to delete %s", file);
  3942.            }
  3943.       }
  3944.      }
  3945.    /* Since we have a decode directory, do not bother saving this */
  3946.    if (NULL != Slrn_Decode_Directory)
  3947.      *Output_Filename = 0;
  3948. }
  3949.  
  3950. /*}}}*/
  3951. #endif  /* SLRN_HAS_DECODE */
  3952.  
  3953. /*}}}*/
  3954. /*{{{ pipe_article functions */
  3955.  
  3956. int slrn_pipe_article_to_cmd (char *cmd) /*{{{*/
  3957. {
  3958. #if SLRN_HAS_PIPING
  3959.    FILE *fp;
  3960.  
  3961.    if (-1 == select_article (1))
  3962.      return -1;
  3963.  
  3964.    if (NULL == (fp = slrn_popen (cmd, "w")))
  3965.      {
  3966.     slrn_error ("Unable to open pipe to %s", cmd);
  3967.     return -1;
  3968.      }
  3969.    
  3970.    if (-1 == write_article_lines (fp))
  3971.      {
  3972.     slrn_pclose (fp);
  3973.     return -1;
  3974.      }
  3975.    
  3976.    slrn_pclose (fp);
  3977.    return 0;
  3978. #else
  3979.    slrn_error ("Piping not implemented on this system.");
  3980.    return -1;
  3981. #endif
  3982. }
  3983.  
  3984. /*}}}*/
  3985.  
  3986. static void pipe_article (void) /*{{{*/
  3987. {
  3988. #if SLRN_HAS_PIPING
  3989.    static char cmd[256];
  3990.    
  3991.    if (slrn_read_input ("Pipe to command", NULL, cmd, 1, 1) <= 0)
  3992.      {
  3993.     slrn_error ("Aborted.  Command name is required.");
  3994.     return;
  3995.      }
  3996.    
  3997.    if (-1 == slrn_pipe_article_to_cmd (cmd))
  3998.      slrn_message ("Error piping to %s.", cmd);
  3999. #else
  4000.    slrn_error ("Piping not implemented on this system.");
  4001. #endif
  4002. }
  4003.  
  4004. /*}}}*/
  4005.  
  4006. static int print_article (void) /*{{{*/
  4007. {
  4008.    Slrn_Print_Type *p;
  4009.    Slrn_Article_Line_Type *l;
  4010.  
  4011.    if (-1 == select_article (1))
  4012.      return -1;
  4013.  
  4014.    slrn_message_now ("Printing article...");
  4015.  
  4016.    p = slrn_open_printer ();
  4017.    if (p == NULL)
  4018.      return -1;
  4019.  
  4020.    l = Slrn_Article_Lines;
  4021.  
  4022.    while (l != NULL)
  4023.      {
  4024.     if ((-1 == slrn_write_to_printer (p, l->buf, strlen (l->buf)))
  4025.         || (-1 == slrn_write_to_printer (p, "\n", 1)))
  4026.       {
  4027.          slrn_close_printer (p);
  4028.          return -1;
  4029.       }
  4030.  
  4031.     l = l->next;
  4032.      }
  4033.  
  4034.    if (-1 == slrn_close_printer (p))
  4035.      return -1;
  4036.  
  4037.    slrn_message_now ("Printing article...done");
  4038.    return 0;
  4039. }
  4040.  
  4041. /*}}}*/
  4042.  
  4043. static void print_article_cmd (void) /*{{{*/
  4044. {
  4045.    if (Slrn_User_Wants_Confirmation
  4046.        && (slrn_get_yesno_cancel ("Are you sure you want to print the article") <= 0))
  4047.      return;
  4048.  
  4049.    (void) print_article ();
  4050. }
  4051.  
  4052. /*}}}*/
  4053.  
  4054.  
  4055. /*}}}*/
  4056.  
  4057. /*{{{ sorting header functions */
  4058.  
  4059. static int subject_cmp (register unsigned char *sa, register unsigned char *sb) /*{{{*/
  4060. {
  4061.    register unsigned char ch;
  4062.    
  4063.    /* skip past re: */
  4064.    while (*sa == ' ') sa++;
  4065.    while (*sb == ' ') sb++;
  4066.  
  4067.    ch = *sa;
  4068.    if (((ch | 0x20) == 'r') && ((*(sa + 1) | 0x20) == 'e')
  4069.        && (*(sa + 2) == ':'))
  4070.      {
  4071.     sa += 3;
  4072.      }
  4073.    
  4074.    ch = *sb;
  4075.    if (((ch | 0x20) == 'r') && ((*(sb + 1) | 0x20) == 'e')
  4076.        && (*(sb + 2) == ':'))
  4077.      {
  4078.     sb += 3;
  4079.      }
  4080.    
  4081.    while (1)
  4082.      {
  4083.     register unsigned char cha, chb;
  4084.  
  4085.     while (*sa == ' ') sa++;
  4086.     while (*sb == ' ') sb++;
  4087.  
  4088.     cha = UPPER_CASE(*sa);
  4089.     chb = UPPER_CASE(*sb);
  4090.     
  4091.     if (cha != chb)
  4092.       return (int) cha - (int) chb;
  4093.     
  4094.     if (cha == 0)
  4095.       return 0;
  4096.     
  4097.     sa++;
  4098.     sb++;    
  4099.      }
  4100.  
  4101.    /* return slrn_case_strcmp ((unsigned char *) sa, (unsigned char *) sb); */
  4102. }
  4103.  
  4104. /*}}}*/
  4105.  
  4106. static int header_subj_cmp (Slrn_Header_Type **ap, Slrn_Header_Type **bp) /*{{{*/
  4107. {
  4108.    int cmp;
  4109.    Slrn_Header_Type *a = *ap, *b = *bp;
  4110.    int ahigh, bhigh;
  4111.    
  4112.    ahigh = (0 != (a->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
  4113.    bhigh = (0 != (b->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
  4114.    
  4115.    if (ahigh == bhigh)
  4116.      {
  4117.     cmp = subject_cmp ((unsigned char *) (a->subject),
  4118.                (unsigned char *) (b->subject));
  4119.     if (!cmp) return a->number - b->number;
  4120.      }
  4121.    else cmp = bhigh - ahigh;
  4122.    
  4123.    return cmp;
  4124. }
  4125.  
  4126. /*}}}*/
  4127.  
  4128. static int header_date_cmp (Slrn_Header_Type **ap, Slrn_Header_Type **bp) /*{{{*/
  4129. {
  4130.    Slrn_Header_Type *a = *ap, *b = *bp;
  4131.    long ahigh, bhigh;
  4132.    
  4133.    ahigh = (0 != (a->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
  4134.    bhigh = (0 != (b->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
  4135.    
  4136.    if (ahigh == bhigh)
  4137.      {
  4138.     ahigh = slrn_date_to_order_parm (a->date);
  4139.     bhigh = slrn_date_to_order_parm (b->date);
  4140.     
  4141.     if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
  4142.       {
  4143.          long tmp = ahigh;
  4144.          ahigh = bhigh;
  4145.          bhigh = tmp;
  4146.       }
  4147.     
  4148.     if (ahigh == bhigh)
  4149.       return a->number - b->number;
  4150.      }
  4151.  
  4152.    return (int) (bhigh - ahigh);
  4153. }
  4154.  
  4155. /*}}}*/
  4156.  
  4157. typedef int (*Header_Cmp_Func_Type)(Slrn_Header_Type **, Slrn_Header_Type **);
  4158.  
  4159. static void sort_by_function (Header_Cmp_Func_Type cmp_func) /*{{{*/
  4160. {
  4161.    Slrn_Header_Type **header_list, *h;
  4162.    unsigned int i, nheaders;
  4163.    void (*qsort_fun) (char *, unsigned int,
  4164.               unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
  4165.    
  4166.    /* This is a silly hack to make up for braindead compilers and the lack of
  4167.     * uniformity in prototypes for qsort.
  4168.     */
  4169.    qsort_fun = (void (*)(char *, unsigned int,
  4170.              unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
  4171.      qsort;
  4172.    
  4173.    /* Count the number we need to sort. */
  4174.    nheaders = 0;
  4175.    h = Slrn_First_Header;
  4176.    while (h != NULL)
  4177.      {
  4178.     if (h->parent == NULL) nheaders++;
  4179.     h = h->real_next;
  4180.      }
  4181.    if (nheaders < 2) return;
  4182.    
  4183.    if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nheaders + 1)))
  4184.      {
  4185.     slrn_error ("sort_headers(): memory allocation failure.");
  4186.     return;
  4187.      }
  4188.    
  4189.    h = Slrn_First_Header;
  4190.    nheaders = 0;
  4191.    while (h != NULL)
  4192.      {
  4193.     if (h->parent == NULL)
  4194.       header_list[nheaders++] = h;
  4195.     h = h->real_next;
  4196.      }
  4197.    header_list[nheaders] = NULL;
  4198.    
  4199.    (*qsort_fun) ((char *) header_list, nheaders, sizeof (Slrn_Header_Type *), cmp_func);
  4200.    
  4201.    /* What to do now depends upon the current threading state. */
  4202.    
  4203.    if (Headers_Threaded == 0)
  4204.      {
  4205.     header_list[0]->next = header_list[1];
  4206.     header_list[0]->prev = NULL;
  4207.     
  4208.     for (i = 1; i < nheaders; i++)
  4209.       {
  4210.          h = header_list[i];
  4211.          h->next = header_list[i + 1];
  4212.          h->prev = header_list[i - 1];
  4213.       }
  4214.      }
  4215.    else
  4216.      {
  4217.     slrn_collapse_threads (0);
  4218.     /* The headers are threaded so we simply have sorted parents.  Arrange
  4219.      * those.
  4220.      */
  4221.     h = NULL;
  4222.     for (i = 0; i <= nheaders; i++)
  4223.       {
  4224.          Slrn_Header_Type *h1 = header_list[i];
  4225.          if (h != NULL)
  4226.            {
  4227.           h->sister = h1;
  4228.           while (h->child != NULL)
  4229.             {
  4230.                h = h->child;
  4231.                while (h->sister != NULL) h = h->sister;
  4232.             }
  4233.           h->next = h1;
  4234.            }
  4235.          if (h1 != NULL) h1->prev = h;
  4236.          h = h1;
  4237.       }
  4238.      }
  4239.    
  4240.    Headers = header_list[0];
  4241.    find_header_line_num ();
  4242.    
  4243.    Slrn_Full_Screen_Update = 1;
  4244.    SLFREE (header_list);
  4245.    
  4246.    if (Slrn_Threads_Visible)
  4247.      {
  4248.     slrn_uncollapse_threads (1);
  4249.      }
  4250. }
  4251.  
  4252. /*}}}*/
  4253.  
  4254. static void sort_by_subject (void) /*{{{*/
  4255. {
  4256.    sort_by_function (header_subj_cmp);
  4257. }
  4258.  
  4259. /*}}}*/
  4260.  
  4261. static void sort_by_date (void) /*{{{*/
  4262. {
  4263.    sort_by_function (header_date_cmp);
  4264. }
  4265.  
  4266. /*}}}*/
  4267.  
  4268. #if SLRN_HAS_SORT_BY_SCORE
  4269. static int header_score_cmp (Slrn_Header_Type **a, Slrn_Header_Type **b) /*{{{*/
  4270. {
  4271.    /* sort by *descending* score */
  4272.    int cmp = (*b)->thread_score - (*a)->thread_score;
  4273.    if (cmp == 0) 
  4274.      {
  4275.     if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
  4276.       return header_subj_cmp (a, b);
  4277.     else 
  4278.       return (*a)->number - (*b)->number;
  4279.      }
  4280.    return cmp;
  4281. }
  4282.  
  4283. /*}}}*/
  4284.  
  4285. static void sort_by_score (void) /*{{{*/
  4286. {
  4287.    sort_by_function (header_score_cmp);
  4288. }
  4289.  
  4290. /*}}}*/
  4291. #endif
  4292.  
  4293.  
  4294. static void sort_by_server_number (void) /*{{{*/
  4295. {
  4296.    Slrn_Header_Type *h;
  4297.    
  4298.    /* This is easy since the real_next, prev are already ordered. */
  4299.    h = Slrn_First_Header;
  4300.    while (h != NULL)
  4301.      {
  4302.     Slrn_Header_Type *next = h->real_next;
  4303.     h->next = h->real_next;
  4304.     h->prev = h->real_prev;
  4305.     h->num_children = 0;
  4306.     h->flags &= ~(HEADER_HIDDEN | ALL_THREAD_FLAGS);
  4307.     h->sister = h->parent = h->child = NULL;
  4308.     h = next;
  4309.      }
  4310.    
  4311.    /* Now find out where to put the Headers pointer */
  4312.    while (Headers->prev != NULL) Headers = Headers->prev;
  4313.    
  4314.    Headers_Threaded = 0;
  4315.    
  4316.    find_header_line_num ();
  4317.    Slrn_Full_Screen_Update = 1;
  4318. }
  4319.  
  4320. /*}}}*/
  4321.  
  4322. static void sort_by_threads (void) /*{{{*/
  4323. {
  4324.    thread_headers ();
  4325.    sort_threads ();
  4326.    
  4327.    if (Slrn_Threads_Visible)
  4328.      {
  4329.     slrn_uncollapse_threads (1);
  4330.      }
  4331.    Slrn_Full_Screen_Update = 1;
  4332.    Headers_Threaded = 1;
  4333. }
  4334.  
  4335. /*}}}*/
  4336.  
  4337. static void toggle_sort (void) /*{{{*/
  4338. {
  4339.    int rsp;
  4340.    
  4341.    rsp = slrn_sbox_sorting_method ();
  4342.    if (rsp != -1)
  4343.      {
  4344.     Slrn_Sorting_Mode = rsp;
  4345.     sort_by_sorting_mode ();
  4346.      }
  4347. }
  4348.  
  4349. /*}}}*/
  4350.  
  4351. static void sort_by_sorting_mode (void) /*{{{*/
  4352. {
  4353.    if ((Slrn_Sorting_Mode & SORT_BY_THREADS) == 0)
  4354.      sort_by_server_number ();
  4355.    else
  4356.      sort_by_threads ();
  4357.    
  4358.    if (Slrn_Sorting_Mode & SORT_BY_DATE)
  4359.      sort_by_date ();
  4360.    else if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
  4361.      sort_by_subject ();
  4362.  
  4363. #if SLRN_HAS_SORT_BY_SCORE
  4364.    if (Slrn_Sorting_Mode & SORT_BY_SCORE)
  4365.      sort_by_score ();
  4366. #endif
  4367. }
  4368.  
  4369. /*}}}*/
  4370.  
  4371. /*}}}*/
  4372. /*{{{ Thread related functions */
  4373.  
  4374. static void find_non_hidden_header (void) /*{{{*/
  4375. {
  4376.    Slrn_Header_Type *h = Slrn_Current_Header;
  4377.    
  4378.    while ((h != NULL) && (h->flags & HEADER_HIDDEN))
  4379.      h = h->prev;
  4380.  
  4381.    if (h == NULL)
  4382.      {
  4383.     h = Slrn_Current_Header;
  4384.     while ((h != NULL) && (h->flags & HEADER_HIDDEN))
  4385.       h = h->next;
  4386.      }
  4387.    
  4388.    Slrn_Current_Header = h;
  4389. }
  4390.  
  4391. /*}}}*/
  4392.  
  4393. /* This function cannot depend upon routines which call SLscroll functions if
  4394.  * sync_now is non-zero.
  4395.  */
  4396. void slrn_collapse_threads (int sync_now) /*{{{*/
  4397. {
  4398.    Slrn_Header_Type *h = Slrn_First_Header;
  4399.    
  4400.    if ((h == NULL) 
  4401.        || (Threads_Collapsed == 1))
  4402.      return;   
  4403.    
  4404.    while (h != NULL)
  4405.      {
  4406.     if (h->parent != NULL) h->flags |= HEADER_HIDDEN;
  4407.     else
  4408.       {
  4409.          h->flags &= ~HEADER_HIDDEN;
  4410.       }
  4411.     h = h->real_next;
  4412.      }
  4413.    
  4414.    find_non_hidden_header ();
  4415.     
  4416.    if (sync_now) find_header_line_num ();
  4417.  
  4418.    Slrn_Full_Screen_Update = 1;
  4419.    Threads_Collapsed = 1;
  4420. }
  4421.  
  4422. /*}}}*/
  4423.  
  4424. void slrn_uncollapse_threads (int sync_now) /*{{{*/
  4425. {
  4426.    Slrn_Header_Type *h = Slrn_First_Header;
  4427.    
  4428.    if ((h == NULL) 
  4429.        || (0 == Threads_Collapsed))
  4430.      return;
  4431.    
  4432.    while (h != NULL)
  4433.      {
  4434.     h->flags &= ~HEADER_HIDDEN;
  4435.     h = h->real_next;
  4436.      }
  4437.    Slrn_Full_Screen_Update = 1;
  4438.    Threads_Collapsed = 0;
  4439.    if (sync_now) find_header_line_num ();
  4440. }
  4441.  
  4442. /*}}}*/
  4443.  
  4444. static void uncollapse_header (Slrn_Header_Type *h) /*{{{*/
  4445. {
  4446.    h->flags &= ~HEADER_HIDDEN;
  4447. }
  4448.  
  4449. /*}}}*/
  4450.  
  4451. static void collapse_header (Slrn_Header_Type *h) /*{{{*/
  4452. {
  4453.    h->flags |= HEADER_HIDDEN;
  4454. }
  4455.  
  4456. /*}}}*/
  4457.  
  4458. static void for_this_tree (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *)) /*{{{*/
  4459. {
  4460.    Slrn_Header_Type *child = h->child;
  4461.    while (child != NULL)
  4462.      {
  4463.     for_this_tree (child, f);
  4464.     child = child->sister;
  4465.      }
  4466.    (*f) (h);
  4467.    Slrn_Full_Screen_Update = 1;
  4468. }
  4469.  
  4470. /*}}}*/
  4471.  
  4472. static void for_this_family (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *)) /*{{{*/
  4473. {
  4474.    while (h != NULL)
  4475.      {
  4476.     for_this_tree (h, f);
  4477.     h = h->sister;
  4478.      }
  4479. }
  4480.  
  4481. /*}}}*/
  4482.  
  4483. void slrn_uncollapse_this_thread (Slrn_Header_Type *h, int sync_linenum) /*{{{*/
  4484. {
  4485.    Slrn_Header_Type *child;
  4486.    
  4487.    /* if (Threads_Collapsed == 0) return; */
  4488.    
  4489.    while (h->parent != NULL) h = h->parent;
  4490.    if ((child = h->child) == NULL) return;
  4491.    if (0 == (child->flags & HEADER_HIDDEN)) return;
  4492.    
  4493.    for_this_family (child, uncollapse_header);
  4494.  
  4495.    if (sync_linenum)
  4496.      find_header_line_num ();
  4497.  
  4498.    Threads_Collapsed = -1;           /* uncertain */
  4499. }
  4500.  
  4501. /*}}}*/
  4502.  
  4503. void slrn_collapse_this_thread (Slrn_Header_Type *h, int sync_linenum) /*{{{*/
  4504. {
  4505.    Slrn_Header_Type *child;
  4506.    
  4507.    /* if (Threads_Collapsed == 1) return; */
  4508.    
  4509.    while (h->parent != NULL) h = h->parent;
  4510.    
  4511.    if ((child = h->child) == NULL) return;
  4512.    if (child->flags & HEADER_HIDDEN) return;
  4513.    
  4514.    for_this_family (child, collapse_header);
  4515.    
  4516.    if (sync_linenum)
  4517.      find_header_line_num ();
  4518.  
  4519.    Threads_Collapsed = -1;           /* uncertain */
  4520. }
  4521.  
  4522. /*}}}*/
  4523.  
  4524. static void toggle_collapse_threads (void) /*{{{*/
  4525. {
  4526.    if (Slrn_Prefix_Arg_Ptr != NULL)
  4527.      {
  4528.     if (Threads_Collapsed == 1)
  4529.       {
  4530.          slrn_uncollapse_threads (0);
  4531.       }
  4532.     else slrn_collapse_threads (0);
  4533.     Slrn_Prefix_Arg_Ptr = NULL;
  4534.      }
  4535.    else
  4536.      {
  4537.     if (0 == slrn_is_thread_collapsed (Slrn_Current_Header))
  4538.       slrn_collapse_this_thread (Slrn_Current_Header, 0);
  4539.     else
  4540.       slrn_uncollapse_this_thread (Slrn_Current_Header, 0);
  4541.     
  4542.     find_non_hidden_header ();
  4543.      }
  4544.    find_header_line_num ();
  4545. }
  4546.  
  4547. /*}}}*/
  4548.  
  4549. static Slrn_Header_Type *sort_thread_node (Slrn_Header_Type *h, char *tree) /*{{{*/
  4550. {
  4551.    Slrn_Header_Type *last = NULL;
  4552.    static unsigned int level;
  4553.    unsigned char vline_char;
  4554.    
  4555.    if (h == NULL) return NULL;
  4556.  
  4557.    vline_char = Graphic_VLine_Char;
  4558.  
  4559.    while (1)
  4560.      {
  4561.     last = h;
  4562.     
  4563.     if (h->child != NULL)
  4564.       {
  4565.          Slrn_Header_Type *child = h->child;
  4566.          unsigned int tree_level;
  4567.          unsigned int save_level = level;
  4568.          
  4569.          h->next = child;
  4570.          child->prev = h;
  4571.          
  4572.          
  4573.          if (level == 0)
  4574.            {
  4575.           if (h->flags & FAKE_CHILDREN)
  4576.             {
  4577.                if ((child->flags & FAKE_PARENT) == 0)
  4578.              {
  4579.                 level = 1;
  4580.              }
  4581.             }
  4582.            }
  4583.          else if (h->flags & FAKE_PARENT)
  4584.            {
  4585.           if (h->sister != NULL) tree[0] = vline_char;
  4586.           else tree[0] = ' ';
  4587.           tree[1] = ' ';
  4588.           level = 1;
  4589.            }
  4590.          
  4591.          tree_level = 2 * level - 2;
  4592.          
  4593.          if (level && (tree_level < sizeof (h->tree) - 2))
  4594.            {
  4595.           if (h->sister != NULL)
  4596.             {
  4597.                if (((h->sister->flags & FAKE_PARENT) == 0)
  4598.                || (h->flags & FAKE_PARENT))
  4599.              {
  4600.                 tree[tree_level] = vline_char;
  4601.              }
  4602.                else tree[tree_level] = ' ';
  4603.             }
  4604.           else
  4605.             {
  4606.                if ((h->parent == NULL) && (h->flags & FAKE_CHILDREN))
  4607.              {
  4608.                 tree[tree_level] = vline_char;
  4609.              }
  4610.                else tree[tree_level] = ' ';
  4611.             }
  4612.           tree[tree_level + 1] = ' ';
  4613.           tree[tree_level + 2] = 0;
  4614.            }
  4615.          
  4616.          level++;
  4617.          last = sort_thread_node (h->child, tree);
  4618.          level--;
  4619.          
  4620.          if (level &&
  4621.          ((tree_level < sizeof (h->tree) - 2)))
  4622.            tree[tree_level] = 0;
  4623.          
  4624.          level = save_level;
  4625.       }
  4626.     
  4627.     if (h->flags & FAKE_PARENT) *tree = 0;
  4628.     
  4629.     if (*tree)
  4630.       {
  4631.          strncpy ((char *) h->tree, tree, sizeof (h->tree) - 1);
  4632.          h->tree[sizeof (h->tree) - 1] = 0;
  4633.       }
  4634.     h = h->sister;
  4635.     last->next = h;
  4636.     if (h == NULL) break;
  4637.     h->prev = last;
  4638.      }
  4639.    return last;
  4640. }
  4641.  
  4642. /*}}}*/
  4643.  
  4644. static unsigned int compute_num_children (Slrn_Header_Type *h) /*{{{*/
  4645. {
  4646.    unsigned int n = 0, dn;
  4647.    
  4648.    h = h->child;
  4649.    while (h != NULL)
  4650.      {
  4651.     n++;
  4652.     if (h->child == NULL) dn = 0;
  4653.     else
  4654.       {
  4655.          dn = compute_num_children (h);
  4656.          n += dn;
  4657.       }
  4658.     h->num_children = dn;
  4659.     h = h->sister;
  4660.      }
  4661.    return n;
  4662. }
  4663.  
  4664. /*}}}*/
  4665.  
  4666. unsigned int slrn_thread_size (Slrn_Header_Type *h)
  4667. {
  4668.    if (h == NULL) return 0;
  4669.    return 1 + compute_num_children (h);
  4670. }
  4671.  
  4672. int slrn_is_thread_collapsed (Slrn_Header_Type *h)
  4673. {
  4674.    if (h == NULL) return 1;
  4675.    while (h->parent != NULL) h = h->parent;
  4676.    if (h->child == NULL) return 0;
  4677.    return (h->child->flags & HEADER_HIDDEN);
  4678. }
  4679.  
  4680.  
  4681. static void sort_threads (void) /*{{{*/
  4682. {
  4683.    Slrn_Header_Type *h;
  4684.    char tree[MAX_TREE_SIZE];
  4685.    
  4686.    h = Slrn_First_Header;
  4687.    Headers = NULL;
  4688.    
  4689.    if (h == NULL) return;
  4690.    while (h != NULL)
  4691.      {
  4692.     if ((h->parent == NULL) && (Headers == NULL))
  4693.       Headers = h;
  4694.     
  4695.     h->prev = h->next = NULL;
  4696.     h->flags &= ~HEADER_HIDDEN;
  4697.     h = h->real_next;
  4698.      }
  4699.    Threads_Collapsed = 0;
  4700.    
  4701.    if (Headers == NULL)
  4702.      slrn_exit_error ("Internal Error.");
  4703.    
  4704.    *tree = 0;
  4705.    sort_thread_node (Headers, tree);
  4706.    while (Headers->prev != NULL) Headers = Headers->prev;
  4707.    
  4708.    slrn_collapse_threads (0);
  4709.    
  4710.    h = Headers;
  4711.    while (h != NULL)
  4712.      {
  4713.     if (h->child == NULL) h->num_children = 0;
  4714.     else
  4715.       {
  4716.          Slrn_Header_Type *next;
  4717.          h->num_children = compute_num_children (h);
  4718.          next = h->next;
  4719.          while ((next != NULL) && (next->parent != NULL))
  4720.            {
  4721. #if SLRN_HAS_SORT_BY_SCORE
  4722.           if (next->flags & HEADER_HIGH_SCORE)
  4723.             h->flags |= FAKE_HEADER_HIGH_SCORE;
  4724.           if (next->score > h->thread_score)
  4725.             h->thread_score = next->score;
  4726. #else
  4727.           if (next->flags & HEADER_HIGH_SCORE)
  4728.             {
  4729.                h->flags |= FAKE_HEADER_HIGH_SCORE;
  4730.                break;
  4731.             }
  4732. #endif
  4733.           next = next->next;
  4734.            }
  4735.       }
  4736.     h = h->sister;
  4737.      }
  4738.    find_header_line_num ();
  4739. }
  4740.  
  4741. /*}}}*/
  4742.  
  4743. static void link_same_subjects (void) /*{{{*/
  4744. {
  4745.    Slrn_Header_Type **header_list, *h;
  4746.    unsigned int i, nparents;
  4747.    void (*qsort_fun) (char *, unsigned int, unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
  4748.    
  4749.    /* This is a silly hack to make up for braindead compilers and the lack of
  4750.     * uniformity in prototypes for qsort.
  4751.     */
  4752.    qsort_fun = (void (*)(char *,
  4753.              unsigned int, unsigned int,
  4754.              int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
  4755.      qsort;
  4756.    
  4757.    h = Slrn_First_Header;
  4758.    nparents = 0;
  4759.    while (h != NULL)
  4760.      {
  4761.     if (h->parent == NULL)
  4762.       nparents++;
  4763.     h = h->real_next;
  4764.      }
  4765.    if (nparents < 2) return;
  4766.    
  4767.    
  4768.    if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nparents)))
  4769.      {
  4770.     slrn_error ("link_same_subjects: memory allocation failure.");
  4771.     return;
  4772.      }
  4773.    
  4774.    h = Slrn_First_Header;
  4775.    i = 0;
  4776.    while (i < nparents)
  4777.      {
  4778.     if (h->parent == NULL) header_list[i++] = h;
  4779.     h = h->real_next;
  4780.      }
  4781.    
  4782.    (*qsort_fun) ((char *) header_list,
  4783.          nparents, sizeof (Slrn_Header_Type *), header_subj_cmp);
  4784.    
  4785.    h = header_list[0];
  4786.    for (i = 1; i < nparents; i++)
  4787.      {
  4788.     Slrn_Header_Type *h1 = header_list[i];
  4789.     if (0 == subject_cmp ((unsigned char *) h->subject,
  4790.                   (unsigned char *) h1->subject))
  4791.       {
  4792.          if (h->child == NULL)
  4793.            {
  4794.           h->child = h1;
  4795.            }
  4796.          else
  4797.            {
  4798.           Slrn_Header_Type *child = h->child;
  4799.           while (child->sister != NULL) child = child->sister;
  4800.           child->sister = h1;
  4801.            }
  4802.          
  4803.          h1->parent = h;
  4804.          h->flags |= FAKE_CHILDREN;
  4805.          h1->flags |= FAKE_PARENT;
  4806.          if (h1->flags & FAKE_CHILDREN)
  4807.            {
  4808.           /* Well, we have to link them up to the new parent.  That
  4809.            * is, h1 will become their sister.  So, extract the
  4810.            * adopted children of h1 and make them the sister,
  4811.            */
  4812.           Slrn_Header_Type *child = h1->child, *last_child;
  4813.           last_child = child;
  4814.           
  4815.           /* child CANNOT be NULL here!! (the parent claims to have
  4816.                           children) */
  4817.           child = child->sister;
  4818.           while (child != NULL)
  4819.             {
  4820.                if (child->flags & FAKE_PARENT)
  4821.              break;
  4822.                last_child = child;
  4823.                child = child->sister;
  4824.             }
  4825.           
  4826.           if (last_child->flags & FAKE_PARENT)
  4827.             {
  4828.                child = last_child;
  4829.                h1->child = NULL;
  4830.             }
  4831.           else last_child->sister = NULL;
  4832.           
  4833.           last_child = child;
  4834.           while (child != NULL)
  4835.             {
  4836.                child->parent = h;
  4837.                /* No need to set fake parent flags since fake children
  4838.             * are all group together.  That is, once you loop
  4839.             * through the sisters and find one, you have found them
  4840.             * all.
  4841.             */
  4842.                child = child->sister;
  4843.             }
  4844.           
  4845.           /* Now h1 will become the sister. */
  4846.           child = h1;
  4847.           while (child->sister != NULL) child = child->sister;
  4848.           child->sister = last_child;
  4849.           h1->flags &= ~FAKE_CHILDREN;
  4850.            }
  4851.       }
  4852.     else h = h1;
  4853.      }
  4854.    SLFREE (header_list);
  4855. }
  4856.  
  4857. /*}}}*/
  4858.  
  4859. typedef struct /*{{{*/
  4860. {
  4861.    unsigned long ref_hash;
  4862.    Slrn_Header_Type *h;
  4863. }
  4864.  
  4865. /*}}}*/
  4866. Relative_Type;
  4867.  
  4868. static void link_lost_relatives (void) /*{{{*/
  4869. {
  4870.    unsigned int n, i, j;
  4871.    Slrn_Header_Type *h;
  4872.    Relative_Type *relatives;
  4873.    
  4874.    /* count the number of possible relatives */
  4875.    n = 0;
  4876.    h = Slrn_First_Header;
  4877.    while (h != NULL)
  4878.      {
  4879.     if ((h->parent == NULL)
  4880.         && (h->refs != NULL)
  4881.         && (*h->refs != 0)) n++;
  4882.     
  4883.     h = h->real_next;
  4884.      }
  4885.    if (n < 2) return;
  4886.    
  4887.    relatives = (Relative_Type *) slrn_malloc (sizeof (Relative_Type) * n, 0, 0);
  4888.    if (relatives == NULL)
  4889.      return;
  4890.    
  4891.    n = 0;
  4892.    h = Slrn_First_Header;
  4893.    while (h != NULL)
  4894.      {
  4895.     if ((h->parent == NULL)
  4896.         && (h->refs != NULL)
  4897.         && (*h->refs != 0))
  4898.       {
  4899.          unsigned char *r, *ref_begin;
  4900.          
  4901.          r = (unsigned char *) h->refs;
  4902.          while (*r && (*r != '<')) r++;
  4903.          if (*r == '<')
  4904.            {
  4905.           ref_begin = r;
  4906.           while (*r && (*r != '>')) r++;
  4907.           if (*r == '>') r++;
  4908.           relatives[n].ref_hash = slrn_compute_hash (ref_begin, r);
  4909.           relatives[n].h = h;
  4910.           n++;
  4911.            }
  4912.       }
  4913.     h = h->real_next;
  4914.      }
  4915.    
  4916.    for (i = 0; i < n; i++)
  4917.      {
  4918.     unsigned long ref_hash;
  4919.     Relative_Type *ri = relatives + i;
  4920.     Slrn_Header_Type *rih;
  4921.     
  4922.     ref_hash = ri->ref_hash;
  4923.     rih = ri->h;
  4924.     
  4925.     for (j = i + 1; j < n; j++)
  4926.       {
  4927.          if (relatives[j].ref_hash == ref_hash)
  4928.            {
  4929.           Slrn_Header_Type *rjh = relatives[j].h;
  4930.           
  4931.           if (Slrn_New_Subject_Breaks_Threads
  4932.               && (rih->subject != NULL)
  4933.               && (rjh->subject != NULL)
  4934.               && (0 != subject_cmp ((unsigned char *)rih->subject, (unsigned char *)rjh->subject)))
  4935.             continue;
  4936.           
  4937.           if (rih->parent != NULL)
  4938.             {
  4939.                rih->sister = rjh;
  4940.                rjh->parent = rih->parent;
  4941.             }
  4942.           else if (rih->child == NULL)
  4943.             {
  4944.                rih->child = rjh;
  4945.                rjh->parent = rih;
  4946.                rih->flags |= FAKE_CHILDREN;
  4947.             }
  4948.           else
  4949.             {
  4950.                Slrn_Header_Type *child = rih->child;
  4951.                /* This is an important step.  All adopted children
  4952.             * get linked to the LAST child.  This ordering
  4953.             * assumption is used elsewhere.
  4954.             */
  4955.                while (child->sister != NULL) child = child->sister;
  4956.                child->sister = rjh;
  4957.                rjh->parent = rih;
  4958.                rih->flags |= FAKE_CHILDREN;
  4959.             }
  4960.           rjh->flags |= FAKE_PARENT;
  4961.           break;
  4962.            }
  4963.       }
  4964.      }
  4965.    SLFREE (relatives);
  4966. }
  4967.  
  4968. /*}}}*/
  4969.  
  4970. static void thread_headers (void) /*{{{*/
  4971. {
  4972.    Slrn_Header_Type *h, *ref;
  4973.    char *r0, *r1, *rmin;
  4974.    
  4975.    make_hash_table ();
  4976.    
  4977.    h = Slrn_First_Header;
  4978.    while (h != NULL)
  4979.      {
  4980.     h->next = h->prev = h->child = h->parent = h->sister = NULL;
  4981.     h->flags &= ~ALL_THREAD_FLAGS;
  4982.     *h->tree = 0;
  4983.     h = h->real_next;
  4984.      }
  4985.    
  4986.    /* SLMEMSET ((char *) Lost_Ancestors, 0, sizeof (Lost_Ancestors)); */
  4987.    
  4988.    h = Slrn_First_Header;
  4989.    while (h != NULL)
  4990.      {
  4991.     if (*h->refs == 0)
  4992.       {
  4993.          h = h->real_next;
  4994.          continue;
  4995.       }
  4996.     
  4997.     rmin = h->refs;
  4998.     r1 = rmin + strlen (rmin);
  4999.     
  5000.     while (1)
  5001.       {
  5002.          while ((r1 > rmin) && (*r1 != '>')) r1--;
  5003.          r0 = r1 - 1;
  5004.          while ((r0 >= rmin) && (*r0 != '<')) r0--;
  5005.          if ((r0 < rmin) || (r1 == rmin)) break;
  5006.          
  5007.          ref = find_header_from_msgid (r0, r1 + 1);
  5008.          
  5009.          if (ref != NULL)
  5010.            {
  5011.           Slrn_Header_Type *child, *rparent;
  5012.           
  5013.           if (Slrn_New_Subject_Breaks_Threads
  5014.               && (h->subject != NULL)
  5015.               && (ref->subject != NULL)
  5016.               && (0 != subject_cmp ((unsigned char *)h->subject, (unsigned char *)ref->subject)))
  5017.             break;
  5018.           
  5019.           rparent = ref;
  5020.           while (rparent->parent != NULL) rparent = rparent->parent;
  5021.           if (rparent == h)
  5022.             {
  5023.                /* self referencing!!! */
  5024.                slrn_error ("Article %d is part of reference loop!", h->number);
  5025.             }
  5026.           else
  5027.             {
  5028.                h->parent = ref;
  5029.                child = ref->child;
  5030.                if (child == NULL) ref->child = h;
  5031.                else
  5032.              {
  5033.                 while (child->sister != NULL) child = child->sister;
  5034.                 child->sister = h;
  5035.              }
  5036.                break;
  5037.             }
  5038.            }
  5039.          r1 = r0;
  5040.       }
  5041.     h = h->real_next;
  5042.      }
  5043.    
  5044.    /* No perform a re-arrangement such that those with the no parents but
  5045.     * share the same reference are placed side-by-side as sisters.
  5046.     */
  5047.    
  5048.    link_lost_relatives ();
  5049.    
  5050.    /* Now perform sort on subject to catch those that have fallen through the
  5051.     * cracks, i.e., no references */
  5052.    link_same_subjects ();
  5053.    
  5054.    /* Now link others up as sisters */
  5055.    h = Slrn_First_Header;
  5056.    while ((h != NULL) && (h->parent != NULL))
  5057.      {
  5058.     h = h->real_next;
  5059.      }
  5060.    
  5061.    while (h != NULL)
  5062.      {
  5063.     Slrn_Header_Type *next;
  5064.     next = h->real_next;
  5065.     while ((next != NULL) && (next->parent != NULL))
  5066.       next = next->real_next;
  5067.     h->sister = next;
  5068.     h = next;
  5069.      }
  5070. }
  5071.  
  5072. /*}}}*/
  5073.  
  5074. /*}}}*/
  5075.  
  5076. /*{{{ select_article */
  5077.  
  5078. /* returns 0 if article selected, -1 if something went wrong or 1 if article
  5079.  * already selected.
  5080.  */
  5081. static int select_article (int do_mime) /*{{{*/
  5082. {
  5083.    int ret = 1;
  5084.    
  5085.    Slrn_Full_Screen_Update = 1;
  5086.    
  5087.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  5088.    
  5089.    if (Slrn_Current_Header != Header_Showing)
  5090.      {
  5091.     if (read_article (Slrn_Current_Header, Slrn_Del_Article_Upon_Read, 1) < 0) return -1;
  5092. #if SLRN_HAS_MIME
  5093.     if (do_mime && Slrn_Use_Mime && Slrn_Mime_Needs_Metamail)
  5094.       {
  5095.          if (slrn_mime_call_metamail ())
  5096.            return -1;
  5097.       }
  5098. #endif
  5099.     ret = 0;
  5100.      }
  5101.    
  5102.    set_article_visibility (1);
  5103.    return ret;
  5104. }
  5105.  
  5106. /*}}}*/
  5107.  
  5108. /*}}}*/
  5109.  
  5110. /*{{{ mark_spot and exchange_mark */
  5111.  
  5112. static void mark_spot (void) /*{{{*/
  5113. {
  5114.    Mark_Header = Slrn_Current_Header;
  5115.    slrn_message ("Mark set.");
  5116. }
  5117.  
  5118. /*}}}*/
  5119.  
  5120.  
  5121. static void exchange_mark (void) /*{{{*/
  5122. {
  5123.    if (Mark_Header == NULL)
  5124.      {
  5125.     slrn_error ("Mark not set.");
  5126.     return;
  5127.      }
  5128.    
  5129.    if (-1 == slrn_goto_header (Mark_Header, 0)) return;
  5130.    mark_spot ();
  5131. }
  5132.  
  5133. /*}}}*/
  5134.  
  5135. /*}}}*/
  5136. /*{{{ subject/author header searching commands */
  5137.  
  5138. static void header_generic_search (int dir, int type) /*{{{*/
  5139. {
  5140.    static char search_str[256];
  5141.    SLsearch_Type st;
  5142.    Slrn_Header_Type *l;
  5143.    char prompt[80];
  5144.    
  5145.    sprintf (prompt, "%s Search %s",
  5146.         type == 's' ? "Subject" : "Author",
  5147.         dir > 0 ? "Forward" : "Backward");
  5148.    
  5149.    if (slrn_read_input (prompt, search_str, NULL, 0, 0) <= 0)
  5150.      return;
  5151.    
  5152.    SLsearch_init (search_str, 1, 0, &st);
  5153.    
  5154.    if (dir > 0) l = Slrn_Current_Header->next;
  5155.    else l = Slrn_Current_Header->prev;
  5156.    
  5157.    while (l != NULL)
  5158.      {
  5159.     if (type == 's')
  5160.       {
  5161.          if ((l->subject != NULL)
  5162.          && (NULL != SLsearch ((unsigned char *) l->subject,
  5163.                        (unsigned char *) l->subject + strlen (l->subject),
  5164.                        &st)))
  5165.            break;
  5166.       }
  5167.     else if ((l->from != NULL)
  5168.          && (NULL != SLsearch ((unsigned char *) l->from,
  5169.                        (unsigned char *) l->from + strlen (l->from),
  5170.                        &st)))
  5171.       break;
  5172.     
  5173.     if (dir > 0) l = l->next; else l = l->prev;
  5174.      }
  5175.    
  5176.    if (l == NULL)
  5177.      {
  5178.     slrn_error ("Not found.");
  5179.     return;
  5180.      }
  5181.    
  5182.    if (l->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (l, 0);
  5183.    Slrn_Current_Header = l;
  5184.    find_header_line_num ();
  5185. }
  5186.  
  5187. /*}}}*/
  5188.  
  5189. static void subject_search_forward (void) /*{{{*/
  5190. {
  5191.    header_generic_search (1, 's');
  5192. }
  5193.  
  5194. /*}}}*/
  5195.  
  5196. static void subject_search_backward (void) /*{{{*/
  5197. {
  5198.    header_generic_search (-1, 's');
  5199. }
  5200.  
  5201. /*}}}*/
  5202.  
  5203. static void author_search_forward (void) /*{{{*/
  5204. {
  5205.    header_generic_search (1, 'a');
  5206. }
  5207.  
  5208. /*}}}*/
  5209.  
  5210. static void author_search_backward (void) /*{{{*/
  5211. {
  5212.    header_generic_search (-1, 'a');
  5213. }
  5214.  
  5215. /*}}}*/
  5216.  
  5217. /*}}}*/
  5218.  
  5219. /*{{{ score header support */
  5220.  
  5221. /*{{{ kill list functions */
  5222.  
  5223.  
  5224. typedef struct Kill_List_Type /*{{{*/
  5225. {
  5226. #define MAX_DKILLS 50
  5227.    int nums[MAX_DKILLS];
  5228.    unsigned int num_used;
  5229.    struct Kill_List_Type *next;
  5230. }
  5231.  
  5232. /*}}}*/
  5233.  
  5234. Kill_List_Type;
  5235.  
  5236. static Kill_List_Type *Kill_List;
  5237. static Kill_List_Type *Missing_Article_List;
  5238.  
  5239. static Kill_List_Type *add_to_specified_kill_list (int num, Kill_List_Type *root) /*{{{*/
  5240. {
  5241.    if (num < 0) return root;
  5242.    
  5243.    if ((root == NULL) || (root->num_used == MAX_DKILLS))
  5244.      {
  5245.     Kill_List_Type *k;
  5246.     k = (Kill_List_Type *) SLMALLOC (sizeof (Kill_List_Type));
  5247.     if (k == NULL) return root;
  5248.     k->num_used = 0;
  5249.     k->next = root;
  5250.     root = k;
  5251.      }
  5252.    root->nums[root->num_used++] = num;
  5253.    return root;
  5254. }
  5255.  
  5256. /*}}}*/
  5257.  
  5258. static void add_to_kill_list (int num) /*{{{*/
  5259. {
  5260.    Kill_List = add_to_specified_kill_list (num, Kill_List);
  5261.    Number_Killed++;
  5262. }
  5263.  
  5264. /*}}}*/
  5265.  
  5266. static void add_to_missing_article_list (int num) /*{{{*/
  5267. {
  5268.    Missing_Article_List = add_to_specified_kill_list (num, Missing_Article_List);
  5269. }
  5270.  
  5271. /*}}}*/
  5272.  
  5273. static void free_specific_kill_list_and_update (Kill_List_Type *k) /*{{{*/
  5274. {
  5275.    while (k != NULL)
  5276.      {
  5277.     Kill_List_Type *next = k->next;
  5278.     unsigned int i, imax = k->num_used;
  5279.     int *nums = k->nums;
  5280.     if (User_Aborted_Group_Read == 0) for (i = 0; i < imax; i++)
  5281.       {
  5282.          slrn_mark_article_as_read (NULL, nums[i]);
  5283.       }
  5284.     SLFREE (k);
  5285.     k = next;
  5286.      }
  5287. }
  5288.  
  5289. /*}}}*/
  5290.  
  5291. static void free_kill_lists_and_update (void) /*{{{*/
  5292. {
  5293.    free_specific_kill_list_and_update (Kill_List);
  5294.    Kill_List = NULL;
  5295.    Number_Killed = 0;
  5296.    free_specific_kill_list_and_update (Missing_Article_List);
  5297.    Missing_Article_List = NULL;
  5298. }
  5299.  
  5300. /*}}}*/
  5301.  
  5302.  
  5303. /*}}}*/
  5304.  
  5305. Slrn_Header_Type *slrn_set_header_score (Slrn_Header_Type *h, 
  5306.                      int score, int apply_kill)
  5307. {
  5308.    if (h == NULL) return NULL;
  5309.    
  5310.    if (score >= Slrn_High_Score_Min)
  5311.      {
  5312.     h->flags &= ~(HEADER_LOW_SCORE);
  5313.     h->flags |= HEADER_HIGH_SCORE;
  5314.     Number_High_Scored++;
  5315.      }
  5316.    else if (score < Slrn_Low_Score_Max)
  5317.      {
  5318.     if ((score <= Slrn_Kill_Score_Max) && apply_kill)
  5319.       {
  5320.          int number = h->number;
  5321.          free_header (h);
  5322.          add_to_kill_list (number);
  5323.          return NULL;
  5324.       }
  5325.     
  5326.     h->flags &= ~(HEADER_HIGH_SCORE);
  5327.     h->flags |= (HEADER_READ | HEADER_LOW_SCORE);
  5328.     Number_Low_Scored++;
  5329.     /* The next line should be made configurable */
  5330.     kill_cross_references (h);
  5331.      }
  5332. #if SLRN_HAS_SORT_BY_SCORE
  5333.    h->thread_score = h->score = score;
  5334. #endif
  5335.    return h;
  5336. }
  5337.  
  5338. /*{{{ apply_score */
  5339. static Slrn_Header_Type *apply_score (Slrn_Header_Type *h) /*{{{*/
  5340. {
  5341.    int score;
  5342.    
  5343.    if (h == NULL) return h;
  5344.    
  5345.    if (Slrn_Apply_Score && Perform_Scoring)
  5346.      score = slrn_score_header (h, Slrn_Current_Group_Name);
  5347.    else score = 0;
  5348.    
  5349.    return slrn_set_header_score (h, score, 1);
  5350. }
  5351.  
  5352. /*}}}*/
  5353.  
  5354. /*}}}*/
  5355.  
  5356. /*{{{ score_headers */
  5357. static void score_headers (void) /*{{{*/
  5358. {
  5359.    Slrn_Header_Type *h = Slrn_First_Header;
  5360.    int percent, last_percent, delta_percent;
  5361.    int num;
  5362.    
  5363.    if ((h == NULL) || (Slrn_Apply_Score == 0)) return;
  5364.    
  5365.    /* slrn_set_suspension (1); */
  5366.    
  5367.    percent = num = 0;
  5368.    delta_percent = (30 * 100) / Total_Num_Headers + 1;
  5369.    last_percent = -delta_percent;
  5370.    
  5371.    while (h != NULL)
  5372.      {
  5373.     Slrn_Header_Type *prev, *next;
  5374.     prev = h->real_prev;
  5375.     next = h->real_next;
  5376.     percent = (100 * num) / Total_Num_Headers;
  5377.     if (percent >= last_percent + delta_percent)
  5378.       {
  5379.          slrn_message_now ("Scoring articles: %2d%%, Killed: %u, High: %u, Low: %u",
  5380.                    percent, Number_Killed, Number_High_Scored, Number_Low_Scored);
  5381.          last_percent = percent;
  5382.       }
  5383.     
  5384.     num++;
  5385.     h = apply_score (h);
  5386.     
  5387.     if (h == NULL)
  5388.       {
  5389.          num--;
  5390.          if (prev == NULL)
  5391.            Slrn_First_Header = next;
  5392.          else
  5393.            prev->next = prev->real_next = next;
  5394.          
  5395.          if (next != NULL)
  5396.            next->prev = next->real_prev = prev;
  5397.       }
  5398.     h = next;
  5399.      }
  5400.    Slrn_Current_Header = Headers = Slrn_First_Header;
  5401.    /* slrn_set_suspension (0); */
  5402. }
  5403.  
  5404. /*}}}*/
  5405.  
  5406. /*}}}*/
  5407.  
  5408. static void create_score (void) /*{{{*/
  5409. {
  5410.    if (Slrn_Batch) return;
  5411.  
  5412.    (void) slrn_edit_score (Slrn_Current_Header, Slrn_Current_Group_Name);
  5413. }
  5414.  
  5415. /*}}}*/
  5416.  
  5417.  
  5418. /*}}}*/
  5419.  
  5420. /*{{{ get headers from server and process_xover */
  5421.  
  5422. static Slrn_Header_Type *process_xover (Slrn_XOver_Type *xov)
  5423. {
  5424.    Slrn_Header_Type *h;
  5425.    
  5426.    h = (Slrn_Header_Type *) slrn_safe_malloc (sizeof (Slrn_Header_Type));
  5427.    
  5428.    slrn_map_xover_to_header (xov, h);
  5429.    
  5430.    if ((Slrn_Score_After_XOver == 0) && Perform_Scoring)
  5431.      {
  5432.     if (NULL == (h = apply_score (h)))
  5433.       return h;
  5434.      }
  5435.    
  5436. #if SLRN_HAS_MIME
  5437.    if (Slrn_Use_Mime)
  5438.      {
  5439.     slrn_rfc1522_decode_string (h->subject);
  5440.     slrn_rfc1522_decode_string (h->from);
  5441.      }
  5442. #endif
  5443. #if SLRN_HAS_GROUPLENS
  5444.    if (Slrn_Use_Group_Lens)
  5445.      {
  5446.     h->gl_rating = h->gl_pred = -1;
  5447.      }
  5448. #endif
  5449.    return h;
  5450. }
  5451.  
  5452.  
  5453. /*}}}*/
  5454.  
  5455. /*{{{ get_headers from server */
  5456. static int get_headers (int min, int max, int *totalp) /*{{{*/
  5457. {
  5458.    Slrn_Header_Type *h;
  5459.    /* int percent, last_percent, dpercent, */
  5460.    int expected_num;
  5461.    int scoring;
  5462.    int total = *totalp;
  5463.    int reads_per_update;
  5464.    int num_processed;
  5465.    int err;
  5466.    Slrn_XOver_Type xov;
  5467.    
  5468.    if (total == 0)
  5469.      return 0;
  5470.    
  5471.    if (SLang_Error == USER_BREAK)
  5472.      return -1;
  5473.    
  5474.    scoring = Perform_Scoring && !Slrn_Score_After_XOver;
  5475.  
  5476.    if ((reads_per_update = Slrn_Reads_Per_Update) < 5)
  5477.      reads_per_update = 50;
  5478.  
  5479.    if (scoring) reads_per_update = reads_per_update / 3 + 1;
  5480.       
  5481.    /* slrn_set_suspension (1); */
  5482.    
  5483.    err = slrn_open_xover (min, max);
  5484.    if (err != OK_XOVER)
  5485.      {
  5486.     if (err == ERR_NOARTIG)           /* no articles in the range */
  5487.       return 0;
  5488.     
  5489.     return -1;
  5490.      }
  5491.    
  5492.    num_processed = 0;
  5493.    expected_num = min;
  5494.    while (slrn_read_xover(&xov) > 0)
  5495.      {
  5496.     int this_num;
  5497.     int num = Total_Num_Headers + Number_Killed + num_processed;
  5498.     
  5499.     if (SLang_Error == USER_BREAK)
  5500.       {
  5501.          if (Slrn_Server_Obj->sv_reset != NULL)
  5502.            Slrn_Server_Obj->sv_reset ();
  5503.          return -1;
  5504.       }
  5505.  
  5506.     this_num = xov.id;
  5507.     
  5508.     if (expected_num != this_num)
  5509.       {
  5510.          int bad_num;
  5511.          
  5512.          total -= (this_num - expected_num);
  5513.          
  5514.          for (bad_num = expected_num; bad_num < this_num; bad_num++)
  5515.            add_to_missing_article_list (bad_num);
  5516.       }
  5517.     
  5518.     expected_num = this_num + 1;
  5519.     
  5520.     h = process_xover (&xov);
  5521.     
  5522.     num++;
  5523.  
  5524.     if ((1 == (num % reads_per_update))
  5525.         && (SLang_Error == 0))
  5526.       {
  5527.          if (scoring)
  5528.            {
  5529.           slrn_message_now ("Headers Received and Scored: %3d/%-3d, Killed: %u, High: %u, Low: %u",
  5530.                     num, total, 
  5531.                     Number_Killed, Number_High_Scored, Number_Low_Scored);
  5532.            }
  5533.          else
  5534.            slrn_message_now ("Headers Received: %2d/%d", num, total);
  5535.       }
  5536.     
  5537.     if (h == NULL) continue;
  5538.     
  5539.     if (Slrn_First_Header == NULL)
  5540.       Slrn_First_Header = Headers = h;
  5541.     else
  5542.       {
  5543.          h->real_next = Slrn_Current_Header->real_next;
  5544.          h->real_prev = Slrn_Current_Header;
  5545.          Slrn_Current_Header->real_next = h;
  5546.          
  5547.          if (h->real_next != NULL)
  5548.            {
  5549.           h->real_next->real_prev = h;
  5550.            }
  5551.       }
  5552.     
  5553.     Slrn_Current_Header = h;
  5554.     num_processed++;
  5555.      }
  5556.    
  5557.    slrn_close_xover ();
  5558.    
  5559.    if (expected_num != max + 1)
  5560.      {
  5561.     int bad_num;
  5562.          
  5563.     total -= (max - expected_num) + 1;
  5564.          
  5565.     for (bad_num = expected_num; bad_num <= max; bad_num++)
  5566.       add_to_missing_article_list (bad_num);
  5567.      }
  5568.    
  5569.    /* slrn_set_suspension (0); */
  5570.    *totalp = total;
  5571.    
  5572.    Total_Num_Headers += num_processed;
  5573.  
  5574.    return (int) num_processed;
  5575. }
  5576.  
  5577. /*}}}*/
  5578.  
  5579.  
  5580. /*}}}*/
  5581.  
  5582.  
  5583. /*}}}*/
  5584. /*{{{ get parent/children headers, etc... */
  5585.  
  5586. /* Nothing is synced by this routine.  It is up to the calling routine. */
  5587. static void insert_header (Slrn_Header_Type *ref) /*{{{*/
  5588. {
  5589.    int n, id;
  5590.    Slrn_Header_Type *h;
  5591.    Slrn_Range_Type *r;
  5592.    
  5593.    ref->hash_next = Header_Table[ref->hash % HEADER_TABLE_SIZE];
  5594.    Header_Table[ref->hash % HEADER_TABLE_SIZE] = ref;
  5595.    
  5596.    n = ref->number;
  5597.    h = Slrn_First_Header;
  5598.    while (h != NULL)
  5599.      {
  5600.     if (h->number >= n)
  5601.       {
  5602.          ref->real_next = h;
  5603.          ref->real_prev = h->real_prev;
  5604.          if (h->real_prev != NULL) h->real_prev->real_next = ref;
  5605.          h->real_prev = ref;
  5606.          
  5607.          if (h == Slrn_First_Header) Slrn_First_Header = ref;
  5608.          if (h == Headers) Headers = ref;
  5609.          
  5610.          break;
  5611.       }
  5612.     h = h->real_next;
  5613.      }
  5614.    
  5615.    if (h == NULL)
  5616.      {
  5617.     h = Slrn_First_Header;
  5618.     while (h->real_next != NULL) h = h->real_next;
  5619.     
  5620.     ref->real_next = NULL;
  5621.     ref->real_prev = h;
  5622.     h->real_next = ref;
  5623.      }
  5624.    
  5625.    if ((id = ref->number) <= 0) return;
  5626.    
  5627.    /* Set the flags for this guy. */
  5628.    r = Current_Group->range.next;
  5629.    while (r != NULL)
  5630.      {
  5631.     if (r->min > id) break;
  5632.     if (r->max >= id)
  5633.       {
  5634.          ref->flags = HEADER_READ;
  5635.          return;
  5636.       }
  5637.     r = r->next;
  5638.      }
  5639.    ref->flags &= ~HEADER_READ;
  5640. }
  5641.  
  5642. /*}}}*/
  5643.  
  5644. /* line number is not synced. */
  5645. static int get_header_by_message_id (char *msgid, 
  5646.                      int no_error_no_thread, 
  5647.                      int query_server) /*{{{*/
  5648. {
  5649.    Slrn_Header_Type *ref;
  5650.    Slrn_XOver_Type xov;
  5651.    
  5652.    if ((msgid == NULL) || (*msgid == 0)) return -1;
  5653.    
  5654.    ref = find_header_from_msgid (msgid, msgid + strlen (msgid));
  5655.    if (ref != NULL)
  5656.      {
  5657.     Slrn_Current_Header = ref;
  5658.     if (no_error_no_thread == 0)
  5659.       find_header_line_num ();
  5660.     Slrn_Full_Screen_Update = 1;
  5661.     return 0;
  5662.      }
  5663.    
  5664.    if (query_server == 0)
  5665.      return -1;
  5666.  
  5667.    slrn_message_now ("Finding %s from server...", msgid);
  5668.    
  5669.    /* Try reading it from the server */
  5670.    if (-1 == slrn_xover_for_msgid (msgid, &xov))
  5671.      {
  5672.     if (no_error_no_thread == 0)
  5673.       {
  5674.          slrn_error ("Article %s not available.", msgid);
  5675.          return -1;
  5676.       }
  5677.     return 1;
  5678.      }
  5679.    
  5680.    ref = process_xover (&xov);
  5681.  
  5682.    if (ref == NULL) return -1;
  5683.    
  5684.    get_header_real_name (ref);
  5685.    
  5686.    insert_header (ref);
  5687.    
  5688.    Slrn_Current_Header = ref;
  5689.    if (no_error_no_thread == 0)
  5690.      {
  5691.     sort_by_sorting_mode ();
  5692.      }
  5693.    return 0;
  5694. }
  5695.  
  5696. /*}}}*/
  5697.  
  5698. /* returns -1 if not implemented or the number of children returned from
  5699.  * the server.  It does not sync line number. 
  5700.  */  
  5701. static int find_children_headers (Slrn_Header_Type *parent) /*{{{*/
  5702. {
  5703.    char buf[NNTP_BUFFER_SIZE];
  5704.    int id_array[1000];
  5705.    int num_ids, i, id;
  5706.    char *fmt = "Finding children from server...[%c]";
  5707.    char *meter_chars = "|/-\\";
  5708.    static unsigned int last_meter_char;
  5709.  
  5710.    if (OK_HEAD != Slrn_Server_Obj->sv_xpat_cmd ("References",
  5711.                         Slrn_Server_Min, Slrn_Server_Max,
  5712.                         parent->msgid))
  5713.      {
  5714.     slrn_error ("Your server does not provide support for this feature.");
  5715.     return -1;
  5716.      }
  5717.    
  5718.    if (meter_chars[last_meter_char] == 0)
  5719.      last_meter_char = 0;
  5720.    
  5721.    slrn_message_now (fmt, meter_chars[last_meter_char]);
  5722.    last_meter_char++;
  5723.    
  5724.    num_ids = 0;
  5725.    while (Slrn_Server_Obj->sv_read_line (buf, sizeof (buf) - 1) != NULL)
  5726.      {
  5727.     if (meter_chars[last_meter_char] == 0)
  5728.       last_meter_char = 0;
  5729.    
  5730.     slrn_message_now (fmt, meter_chars[last_meter_char]);
  5731.     last_meter_char++;
  5732.     
  5733.     id = atoi (buf);
  5734.     if (id <= 0) continue;
  5735.     
  5736.     if (NULL != find_header_from_serverid (id)) continue;
  5737.     id_array[num_ids] = id;
  5738.     num_ids++;
  5739.      }
  5740.    
  5741.    for (i = 0; i < num_ids; i++)
  5742.      {
  5743.     Slrn_XOver_Type xov;
  5744.     
  5745.     id = id_array[i];
  5746.  
  5747.     if (OK_XOVER != slrn_open_xover (id, id))
  5748.       break;
  5749.     
  5750.     /* This will loop once. */
  5751.     while (slrn_read_xover (&xov) > 0)
  5752.       {
  5753.          Slrn_Header_Type *bad_h, *h;
  5754.          
  5755.          h = process_xover (&xov);
  5756.          if (h == NULL) continue;
  5757.          
  5758.          /* We may already have this header.  How is this possible?
  5759.           * If the header was retrieved sometime earlier via HEAD
  5760.           * <msgid>, the server may not have returned the article
  5761.           * number.  As a result, that header may have a number
  5762.           * of -1.  Here, we really have the correct article
  5763.           * number since the previous while loop made sure of that.
  5764.           * So, before inserting it, check to see whether or not we
  5765.           * have it and if so, fixup the id.
  5766.           */
  5767.          
  5768.          bad_h = slrn_find_header_with_msgid (h->msgid);
  5769.          if (bad_h != NULL)
  5770.            {
  5771.           bad_h->number = h->number;
  5772.           free_header (h);
  5773.           continue;
  5774.            }
  5775.  
  5776.          get_header_real_name (h);
  5777.          insert_header (h);
  5778.       }
  5779.     slrn_close_xover ();
  5780.      }
  5781.    return num_ids;
  5782. }
  5783.  
  5784. /*}}}*/
  5785.  
  5786. /* Line number not synced. */
  5787. static void get_children_headers_1 (Slrn_Header_Type *h) /*{{{*/
  5788. {
  5789.    while (h != NULL)
  5790.      {
  5791.     (void) find_children_headers (h);
  5792.     if (h->child != NULL)
  5793.       {
  5794.          get_children_headers_1 (h->child);
  5795.       }
  5796.     h = h->sister;
  5797.      }
  5798. }
  5799.  
  5800. /*}}}*/
  5801.  
  5802. static void get_children_headers (void) /*{{{*/
  5803. {
  5804.    Slrn_Header_Type *h;
  5805.    
  5806.    /* slrn_set_suspension (1); */
  5807.    
  5808.    if (find_children_headers (Slrn_Current_Header) < 0)
  5809.      {
  5810.     /* slrn_set_suspension (0); */
  5811.     return;
  5812.      }
  5813.    
  5814.    sort_by_sorting_mode ();
  5815.    
  5816.    h = Slrn_Current_Header->child;
  5817.    if (h != NULL) 
  5818.      {
  5819.     /* Now walk the tree getting children headers.  For efficiency,
  5820.      * only children currently threaded will be searched.  Hopefully the
  5821.      * above attempt got everything.  If other newsreaders did not chop off
  5822.      * headers, this would be unnecessary!
  5823.      */
  5824.     get_children_headers_1 (h);
  5825.     sort_by_sorting_mode ();
  5826.      }
  5827.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  5828.    
  5829.    /* slrn_set_suspension (0); */
  5830. }
  5831.  
  5832. /*}}}*/
  5833.  
  5834. static void get_parent_header (void) /*{{{*/
  5835. {
  5836.    char *r1, *r0, *rmin;
  5837.    unsigned int len;
  5838.    char buf[512];
  5839.    int no_error_no_thread;
  5840.    Slrn_Header_Type *last_header;
  5841.    
  5842.    if (Slrn_Current_Header == NULL) return;
  5843.    
  5844.    if (Slrn_Prefix_Arg_Ptr == NULL) no_error_no_thread = 0;
  5845.    else no_error_no_thread = 1;
  5846.    
  5847.    last_header = NULL;
  5848.    r1 = rmin = NULL;
  5849.    do
  5850.      {
  5851.     rmin = Slrn_Current_Header->refs;
  5852.     if (rmin == NULL) break;
  5853.     
  5854.     if (last_header != Slrn_Current_Header)
  5855.       {
  5856.          last_header = Slrn_Current_Header;
  5857.          r1 = rmin + strlen (rmin);
  5858.       }
  5859.     
  5860.     while ((r1 > rmin) && (*r1 != '>')) r1--;
  5861.     r0 = r1 - 1;
  5862.     while ((r0 >= rmin) && (*r0 != '<')) r0--;
  5863.     
  5864.     if ((r0 < rmin) || (r1 == rmin))
  5865.       {
  5866.          if (no_error_no_thread) break;
  5867.          slrn_error ("Article has no parent reference.");
  5868.          return;
  5869.       }
  5870.     
  5871.     len = (unsigned int) ((r1 + 1) - r0);
  5872.     strncpy (buf, r0, len);
  5873.     buf[len] = 0;
  5874.     r1 = r0;
  5875.      }
  5876.    while ((get_header_by_message_id (buf, no_error_no_thread, 1) >= 0)
  5877.       && no_error_no_thread);
  5878.    
  5879.    if (no_error_no_thread)
  5880.      {
  5881.     sort_by_sorting_mode ();
  5882.     if (SLKeyBoard_Quit == 0) get_children_headers ();
  5883.      }
  5884.    
  5885.    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  5886.    slrn_chmap_fix_headers ();  /* We didn't decode parent headers */
  5887. }
  5888.  
  5889. /*}}}*/
  5890.  
  5891. int slrn_locate_header_by_msgid (char *msgid, int query_server)
  5892. {
  5893.    if (0 == get_header_by_message_id (msgid, 0, query_server))
  5894.      {
  5895.     /* The actual header might be part of a collapsed thread.  If so, then
  5896.      * the current header may not be the one we are seeking.
  5897.      * Check the message-id and retry with the thread uncollapsed
  5898.      * if this is the case. 
  5899.      */
  5900.     if ((Slrn_Current_Header->msgid == NULL)
  5901.         || (0 != strcmp (Slrn_Current_Header->msgid, msgid)))
  5902.       {
  5903.          slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
  5904.          (void) get_header_by_message_id (msgid, 0, 0);
  5905.       }
  5906.     return 0;
  5907.      }
  5908.    return -1;
  5909. }
  5910.  
  5911.  
  5912. static void locate_header_by_msgid (void) /*{{{*/
  5913. {
  5914.    char msgid[256];
  5915.    *msgid = 0;
  5916.    if (slrn_read_input ("Enter Message-Id", NULL, msgid, 1, 0) <= 0) return;
  5917.    
  5918.    (void) slrn_locate_header_by_msgid (msgid, 1);
  5919. }
  5920.  
  5921. /*}}}*/
  5922.  
  5923.  
  5924. /*}}}*/
  5925.  
  5926. /*{{{ article window display modes */
  5927.  
  5928. static void hide_article (void) /*{{{*/
  5929. {
  5930.    Slrn_Full_Screen_Update = 1;
  5931.    if (Article_Visible == 0)
  5932.      {
  5933.     select_article (1);
  5934.     return;
  5935.      }
  5936.    
  5937.    set_article_visibility (0);
  5938.    Article_Window_HScroll = 0;
  5939. }
  5940.  
  5941. /*}}}*/
  5942.  
  5943. static void zoom_article_window (void)
  5944. {
  5945.    int zoomed_rows;
  5946.  
  5947.    if (Article_Visible == 0)
  5948.      hide_article ();
  5949.  
  5950.    if (Article_Visible == 0)
  5951.      return;
  5952.    
  5953.    zoomed_rows = SLtt_Screen_Rows - 3;
  5954.    if (zoomed_rows == Article_Window_Nrows)
  5955.      /* already zoomed.  Unzoom */
  5956.      Article_Window_Nrows = 0;
  5957.    else
  5958.      Article_Window_Nrows = zoomed_rows;
  5959.  
  5960.    art_winch ();
  5961. }
  5962.  
  5963. int slrn_is_article_win_zoomed (void)
  5964. {
  5965.    return (Article_Window_Nrows == SLtt_Screen_Rows - 3);
  5966. }
  5967.  
  5968. static void art_left (void) /*{{{*/
  5969. {
  5970.    if ((Article_Visible == 0)
  5971.        || (Article_Window_HScroll == 0))
  5972.      {
  5973.     if (Header_Window_HScroll == 0) return;
  5974.     Header_Window_HScroll -= SLtt_Screen_Cols / 5;
  5975.     if (Header_Window_HScroll < 0) Header_Window_HScroll = 0;
  5976.      }
  5977.    else
  5978.      {
  5979.     if (Article_Window_HScroll == 0) return;
  5980.     Article_Window_HScroll -= (SLtt_Screen_Cols * 2) / 3;
  5981.     if (Article_Window_HScroll < 0) Article_Window_HScroll = 0;
  5982.      }
  5983.    Slrn_Full_Screen_Update = 1;
  5984. }
  5985.  
  5986. /*}}}*/
  5987.  
  5988. static void art_right (void) /*{{{*/
  5989. {
  5990.    if (Article_Visible == 0)
  5991.      Header_Window_HScroll += SLtt_Screen_Cols / 5;
  5992.    else
  5993.      Article_Window_HScroll += (SLtt_Screen_Cols * 2) / 3;
  5994.  
  5995.    Slrn_Full_Screen_Update = 1;
  5996. }
  5997.  
  5998. /*}}}*/
  5999.  
  6000. /*{{{ rot13 and spoilers */
  6001. static void toggle_rot13 (void) /*{{{*/
  6002. {
  6003.    Do_Rot13 = !Do_Rot13;
  6004.    Slrn_Full_Screen_Update = 1;
  6005. }
  6006.  
  6007. /*}}}*/
  6008.  
  6009.  
  6010. #if SLRN_HAS_SPOILERS
  6011. static void show_spoilers (void) /*{{{*/
  6012. {
  6013.    Slrn_Article_Line_Type *l1 = NULL;
  6014.    Slrn_Article_Line_Type *l = Slrn_Article_Lines;
  6015.    
  6016.    do
  6017.      {
  6018.     /* first find the first spoiler-ed line */
  6019.     while ((l != NULL) && 
  6020.            (0 == (l->flags & SPOILER_LINE)))
  6021.       {
  6022.          l = l->next;
  6023.       }
  6024.     if (l1 == NULL) l1 = l;
  6025.     /* now un-spoiler until we hit an un-spoiler-ed line */
  6026.     while ((l != NULL) && (l->flags & SPOILER_LINE))
  6027.       {
  6028.          l->flags &= ~SPOILER_LINE;
  6029.          l = l->next;
  6030.       }
  6031.      } /* Prefix arg means un-spoiler the whole article */
  6032.    while ((l != NULL)
  6033.       && ((Slrn_Prefix_Arg_Ptr != NULL) 
  6034.           || (Slrn_Spoiler_Display_Mode & 2)));
  6035.  
  6036.    Slrn_Prefix_Arg_Ptr = NULL;
  6037.    
  6038.    if ((Slrn_Spoiler_Display_Mode & 1) && (l1 != NULL))
  6039.      {
  6040.     Article_Current_Line = l1;
  6041.     find_article_line_num ();
  6042.      }
  6043.    
  6044.    Slrn_Full_Screen_Update = 1;
  6045. }
  6046.  
  6047. /*}}}*/
  6048.  
  6049. #endif
  6050.  
  6051.  
  6052. /*}}}*/
  6053.  
  6054. /*{{{ hide/toggle quotes */
  6055.  
  6056. /* Does NOT update article line number */
  6057. static void hide_quotes (void) /*{{{*/
  6058. {
  6059.    Slrn_Article_Line_Type *l = Slrn_Article_Lines, *last = NULL;
  6060.    while (l != NULL)
  6061.      {
  6062.     if (l->flags & QUOTE_LINE)
  6063.       {
  6064.          if (Quotes_Hidden && (last != NULL)) l->flags |= HIDDEN_LINE;
  6065.          else l->flags &= ~HIDDEN_LINE;
  6066.          last = l;
  6067.       }
  6068.     else last = NULL;
  6069.     l = l->next;
  6070.      }
  6071.    Slrn_Full_Screen_Update = 1;
  6072. }
  6073.  
  6074. /*}}}*/
  6075.  
  6076. /* This function needs generalized on an article by article basis */
  6077. static void toggle_quotes (void) /*{{{*/
  6078. {
  6079.    Quotes_Hidden = !Quotes_Hidden;
  6080.    hide_quotes ();
  6081.    find_article_line_num ();
  6082. }
  6083.  
  6084. /*}}}*/
  6085.  
  6086. /*}}}*/
  6087.  
  6088.  
  6089. static void toggle_headers (void) /*{{{*/
  6090. {
  6091.    Headers_Hidden_Mode = !Headers_Hidden_Mode;
  6092.    hide_art_headers ();
  6093.    find_article_line_num ();
  6094.    if (Headers_Hidden_Mode == 0) art_bob ();
  6095.    Slrn_Full_Screen_Update = 1;
  6096. }
  6097.  
  6098. /*}}}*/
  6099.  
  6100. /*}}}*/
  6101.  
  6102. /*{{{ leave/suspend article mode and support functions */
  6103. #if 0
  6104. static void update_ranges (void) /*{{{*/
  6105. {
  6106.    int min, max;
  6107.    int queued = 0;
  6108.    Slrn_Range_Type *r, *r_save;
  6109.    Slrn_Header_Type *h = Slrn_First_Header;
  6110.    int save_min, save_max, save_dirty;
  6111.    
  6112.    if (User_Aborted_Group_Read) return;
  6113.    
  6114.    /* skip articles for which the numeric id was not available */
  6115.    while ((h != NULL) && (h->number < 0)) h = h->real_next;
  6116.    if (h == NULL) return;
  6117.    
  6118.    /* we are creating new ranges so steal old. */
  6119.    r = Current_Group->range.next;
  6120.    Current_Group->range.next = NULL;
  6121.    
  6122.    /* Save the range context because we will do a comparison to see whether
  6123.     * or not the group was modified.
  6124.     */
  6125.    r_save = r;
  6126.    save_min = Current_Group->range.min;
  6127.    save_max = Current_Group->range.max;
  6128.    save_dirty = Slrn_Groups_Dirty;
  6129.    
  6130.    /* fill in range for articles prior to current group of headers.  This
  6131.     * is done in two parts.  First, mark as read everything up to the
  6132.     * first article on the server plus what we have read after that.  Then,
  6133.     * fill in the gap for articles on the server up to this group.
  6134.     */
  6135.    
  6136.    max = Slrn_Server_Min - 1;
  6137.    min = h->number;           /* starting number of headers */
  6138.    
  6139.    /* Part one.  Skip past ranges that are below the server minimum number.
  6140.     * They will be fused together.
  6141.     */
  6142.    while ((r != NULL) && (r->min <= max))
  6143.      {
  6144.     if (r->max >= max)
  6145.       {
  6146.          max = r->max;
  6147.          if (max >= min) max = min - 1;
  6148.          r = r->next;
  6149.          break;
  6150.       }
  6151.     r = r->next;
  6152.      }
  6153.    slrn_add_group_ranges (Current_Group, 1, max);
  6154.    
  6155.    /* second part */
  6156.    while ((r != NULL) && (r->min < min))
  6157.      {
  6158.     max = r->max;
  6159.     if (max >= min) max = min - 1;
  6160.     slrn_add_group_ranges (Current_Group, r->min, max);
  6161.     r = r->next;
  6162.      }
  6163.    
  6164.    /* Now handle ranges in the current group.
  6165.     */
  6166.    
  6167.    queued = 0;
  6168.    max = Slrn_Server_Max;
  6169.    while (h != NULL)
  6170.      {
  6171.     max = h->number;
  6172.     if (h->flags & HEADER_READ)
  6173.       {
  6174.          queued = 1;
  6175.       }
  6176.     else
  6177.       {
  6178.          if (queued || (min != max))
  6179.            {
  6180.           slrn_add_group_ranges (Current_Group, min, max - 1);
  6181.           queued = 0;
  6182.            }
  6183.          
  6184.          min = max + 1;           /* mark next number as start of
  6185.                     * a read range.  We mark it now
  6186.                     * because h->next->number might not
  6187.                     * be max + 1.
  6188.                     */
  6189.       }
  6190.     h = h->real_next;
  6191.      }
  6192.    
  6193.    
  6194.    if (queued == 0) min = max + 1;
  6195.    slrn_add_group_ranges (Current_Group, min, Slrn_Server_Max);
  6196.    
  6197.    Slrn_Groups_Dirty = 1;
  6198.    
  6199.    if ((save_dirty == 0) 
  6200.        && (Current_Group->range.min == save_min) 
  6201.        && (Current_Group->range.max == save_max)
  6202.        && (Current_Group->range.next != r_save))
  6203.      {
  6204.     /* Compare the newly constructed ranges to the old.  If they differ
  6205.      * then things were changed.
  6206.      */
  6207.     Slrn_Range_Type *new_r;
  6208.     
  6209.     new_r = Current_Group->range.next;
  6210.     r = r_save;
  6211.     
  6212.     while ((new_r != NULL) && (r != NULL)
  6213.            && (new_r->min == r->min)
  6214.            && (new_r->max == r->max))
  6215.       {
  6216.          new_r = new_r->next;
  6217.          r = r->next;
  6218.       }
  6219.     
  6220.     if ((new_r == NULL) && (r == NULL))
  6221.       Slrn_Groups_Dirty = save_dirty;
  6222.      }
  6223.    
  6224.    r = r_save;
  6225.    while (r != NULL)
  6226.      {
  6227.     r_save = r->next;
  6228.     SLFREE (r);
  6229.     r = r_save;
  6230.      }
  6231. }
  6232.  
  6233. /*}}}*/
  6234. #else
  6235. static void update_ranges (void) /*{{{*/
  6236. {
  6237.    int min, max;
  6238.    Slrn_Range_Type *r, *r_save;
  6239.    Slrn_Header_Type *h, *h1;
  6240.    int save_min, save_max, save_dirty;
  6241.    char *list;
  6242.    unsigned int i, imax, num;
  6243.  
  6244.    if (User_Aborted_Group_Read) return;
  6245.  
  6246.    h = Slrn_First_Header;
  6247.    /* skip articles for which the numeric id was not available */
  6248.    while ((h != NULL) && (h->number < 0)) h = h->real_next;
  6249.    if (h == NULL) return;
  6250.  
  6251.    /* Create a list of read articles and compare them with what
  6252.     * is in the group range.  First find the min and max possible 
  6253.     * article numbers.
  6254.     */
  6255.  
  6256.  
  6257.    min = Slrn_Server_Min;
  6258.    max = Slrn_Server_Max;
  6259.    
  6260.    if (max < min) max = min;
  6261.    num = (unsigned int) (max - min + 1);
  6262.  
  6263.    list = slrn_safe_malloc (num);
  6264.    
  6265.    /* Mark all numbers in the list as unread (0) */
  6266.    memset (list, 0, num);
  6267.  
  6268.    /* Now make list consistent with the already read group ranges */
  6269.    r = Current_Group->range.next;
  6270.    while (r != NULL)
  6271.      {
  6272.     int this_max, this_min;
  6273.  
  6274.     this_min = r->min;
  6275.     this_max = r->max;
  6276.  
  6277.     if (this_min < min)
  6278.       this_min = min;
  6279.     if (this_max > max)
  6280.       max = this_max;
  6281.     
  6282.     if (this_max >= min)
  6283.       {
  6284.          i = (unsigned int) (this_min - min);
  6285.          imax = (unsigned int) (this_max - min);
  6286.          while (i <= imax)
  6287.            {
  6288.           list[i] = 1;
  6289.           i++;
  6290.            }
  6291.       }
  6292.     r = r->next;
  6293.      }
  6294.  
  6295.    /* Use the current headers to fixup the list */
  6296.    h1 = h;
  6297.    while (h1 != NULL)
  6298.      {
  6299.     if ((h1->number >= min) && (h1->number <= max))
  6300.       list[h1->number - min] = (h1->flags & HEADER_READ);
  6301.     h1 = h1->real_next;
  6302.      }
  6303.  
  6304.    /* Finally, update the ranges based on this list */
  6305.  
  6306.    /* we are creating new ranges so steal old and save the range context
  6307.     * because we will do a comparison to see whether or not the group
  6308.     * was modified. 
  6309.     */
  6310.    r_save = Current_Group->range.next;
  6311.    Current_Group->range.next = NULL;
  6312.    save_min = Current_Group->range.min;
  6313.    save_max = Current_Group->range.max;
  6314.    save_dirty = Slrn_Groups_Dirty;
  6315.    
  6316.    /* Mark all articles up to the start of the list as read */
  6317.    slrn_add_group_ranges (Current_Group, 1, min - 1);
  6318.  
  6319.    i = 0;
  6320.    while (i < num)
  6321.      {
  6322.     if (list[i] == 0)
  6323.       {
  6324.          i++;
  6325.          continue;
  6326.       }
  6327.  
  6328.     imax = i;
  6329.     while ((imax < num) && (list[imax] != 0))
  6330.       imax++;
  6331.  
  6332.     slrn_add_group_ranges (Current_Group, 
  6333.                    min + (int)i, min + (int) (imax - 1));
  6334.     
  6335.     i = imax;
  6336.      }
  6337.    
  6338.    Slrn_Groups_Dirty = 1;
  6339.    
  6340.    if ((save_dirty == 0) 
  6341.        && (Current_Group->range.min == save_min) 
  6342.        && (Current_Group->range.max == save_max)
  6343.        && (Current_Group->range.next != NULL))
  6344.      {
  6345.     /* Compare the newly constructed ranges to the old.  If they differ
  6346.      * then things were changed.
  6347.      */
  6348.     Slrn_Range_Type *new_r;
  6349.     
  6350.     new_r = Current_Group->range.next;
  6351.     r = r_save;
  6352.  
  6353.     while ((new_r != NULL) && (r != NULL)
  6354.            && (new_r->min == r->min)
  6355.            && (new_r->max == r->max))
  6356.       {
  6357.          new_r = new_r->next;
  6358.          r = r->next;
  6359.       }
  6360.     
  6361.     if ((new_r == NULL) && (r == NULL))
  6362.       Slrn_Groups_Dirty = save_dirty;
  6363.      }
  6364.  
  6365.    /* Finally delete the saved ranges */
  6366.    r = r_save;
  6367.    while (r != NULL)
  6368.      {
  6369.     r_save = r->next;
  6370.     slrn_free ((char *) r);
  6371.     r = r_save;
  6372.      }
  6373.    slrn_free (list);
  6374. }
  6375. /*}}}*/
  6376. #endif
  6377.  
  6378.  
  6379. /*{{{ art_quit */
  6380. static void art_quit (void) /*{{{*/
  6381. {
  6382.    Slrn_Header_Type *h = Headers, *next;
  6383.  
  6384. #if SLRN_HAS_SLANG
  6385.    (void) SLang_run_hooks ("article_mode_quit_hook", 0);
  6386. #endif
  6387.  
  6388.    slrn_init_hangup_signals (0);
  6389.  
  6390. #if SLRN_HAS_GROUPLENS
  6391.    if (Slrn_Use_Group_Lens) slrn_put_grouplens_scores ();
  6392. #endif
  6393.    
  6394.    free_article ();
  6395.    
  6396.    free_kill_lists_and_update ();
  6397.    free_tag_list ();
  6398.    
  6399.    slrn_close_score ();
  6400.    
  6401.    if (h != NULL)
  6402.      {
  6403.     update_ranges ();
  6404.      }
  6405.    
  6406.    while (h != NULL)
  6407.      {
  6408.     next = h->next;
  6409.     free_header (h);
  6410.     h = next;
  6411.      }
  6412.    
  6413.    Slrn_First_Header = Headers = Slrn_Current_Header = NULL;
  6414.    SLMEMSET ((char *) &Slrn_Header_Window, 0, sizeof (SLscroll_Window_Type));
  6415.    Total_Num_Headers = 0;
  6416.    
  6417.    Current_Group = NULL;
  6418.    Last_Read_Header = NULL;
  6419.    *Output_Filename = 0;
  6420.  
  6421.    Same_Subject_Start_Header = NULL;
  6422.    Slrn_Current_Group_Name = NULL;
  6423.  
  6424.    /* Since this function may get called before the mode is pushed, 
  6425.     * only pop it if the mode is really article mode.
  6426.     */
  6427.    if ((Slrn_Current_Mode != NULL)
  6428.        && (Slrn_Current_Mode->mode == SLRN_ARTICLE_MODE))
  6429.      slrn_pop_mode ();
  6430.  
  6431.    slrn_init_hangup_signals (1);
  6432. }
  6433.  
  6434. /*}}}*/
  6435.  
  6436. /*}}}*/
  6437.  
  6438. static void skip_to_next_group (void) /*{{{*/
  6439. {
  6440.    art_quit ();
  6441.    slrn_select_next_group ();
  6442. }
  6443.  
  6444. /*}}}*/
  6445.  
  6446. static void skip_to_prev_group (void) /*{{{*/
  6447. {
  6448.    art_quit ();
  6449.    slrn_select_prev_group ();
  6450. }
  6451.  
  6452. /*}}}*/
  6453.  
  6454. static void fast_quit (void) /*{{{*/
  6455. {
  6456.    art_quit ();
  6457.    slrn_group_quit ();
  6458. }
  6459.  
  6460. /*}}}*/
  6461.  
  6462. static void art_suspend_cmd (void) /*{{{*/
  6463. {
  6464.    int rows = SLtt_Screen_Rows;
  6465.    slrn_suspend_cmd ();
  6466.    if (rows != SLtt_Screen_Rows)
  6467.      art_winch_sig (rows, -1);
  6468. }
  6469.  
  6470. /*}}}*/
  6471.  
  6472. /*}}}*/
  6473. /*{{{ art_xpunge */
  6474. static void art_xpunge (void) /*{{{*/
  6475. {
  6476.    Slrn_Header_Type *save, *next, *h;
  6477.    
  6478.    free_article ();
  6479.    free_kill_lists_and_update ();
  6480.    
  6481.    save = Headers;
  6482.    if (Headers != NULL)
  6483.      {
  6484.     update_ranges ();
  6485.      }
  6486.    
  6487.    while (Headers != NULL)
  6488.      {
  6489.     if (0 == (Headers->flags & HEADER_READ))
  6490.       break;
  6491.     Headers = Headers->next;
  6492.      }
  6493.    
  6494.    if (Headers == NULL)
  6495.      {
  6496.     Headers = save;
  6497.     art_quit ();
  6498.     return;
  6499.      }
  6500.    
  6501.    if ((Num_Tag_List.len != 0)
  6502.        && (Num_Tag_List.headers != NULL))
  6503.      {
  6504.     unsigned int i, j;
  6505.     Slrn_Header_Type *th;
  6506.     
  6507.     j = 0;
  6508.     for (i = 0; i < Num_Tag_List.len; i++)
  6509.       {
  6510.          th = Num_Tag_List.headers[i];
  6511.          if (th->flags & HEADER_READ)
  6512.            {
  6513.           th->tag_number = 0;
  6514.           th->flags &= ~HEADER_NTAGGED;
  6515.           continue;
  6516.            }
  6517.          
  6518.          Num_Tag_List.headers [j] = th;
  6519.          j++;
  6520.          th->tag_number = j;
  6521.       }
  6522.     Num_Tag_List.len = j;
  6523.      }
  6524.    
  6525.    next = Slrn_Current_Header;
  6526.    while (next != NULL)
  6527.      {
  6528.     if (0 == (next->flags & HEADER_READ))
  6529.       break;
  6530.     next = next->next;
  6531.      }
  6532.    
  6533.    if (next == NULL)
  6534.      {
  6535.     next = Slrn_Current_Header;
  6536.     while (next != NULL)
  6537.       {
  6538.          if (0 == (next->flags & HEADER_READ))
  6539.            break;
  6540.          next = next->prev;
  6541.       }
  6542.      }
  6543.    
  6544.    Slrn_Current_Header = next;           /* cannot be NULL */
  6545.    
  6546.    h = Slrn_First_Header;
  6547.    /* h cannot be NULL here*/
  6548.    while (1)
  6549.      {
  6550.     next = h->real_next;
  6551.     if (0 == (h->flags & HEADER_READ))
  6552.       break;
  6553.     free_header (h);
  6554.     h = next;
  6555.      }
  6556.    Slrn_First_Header = h;
  6557.    h->real_prev = NULL;
  6558.    
  6559.    while (h != NULL)
  6560.      {    
  6561.     Slrn_Header_Type *next_next;
  6562.  
  6563.     next = h->real_next;
  6564.     while (next != NULL)
  6565.       {
  6566.          next_next = next->real_next;
  6567.          if (0 == (next->flags & HEADER_READ))
  6568.            break;
  6569.          free_header (next);
  6570.          next = next_next;
  6571.       }
  6572.     h->real_next = next;
  6573.     if (next != NULL)
  6574.       next->real_prev = h;
  6575.     h = next;
  6576.      }
  6577.    
  6578.    Last_Read_Header = NULL;
  6579.    h = Headers = Slrn_First_Header;
  6580.    Headers->prev = NULL;
  6581.    
  6582.    while (h != NULL)
  6583.      {
  6584.     h->next = next = h->real_next;
  6585.     if (next != NULL)
  6586.       {
  6587.          next->prev = h;
  6588.       }
  6589.     h = next;
  6590.      }
  6591.    
  6592.    sort_by_sorting_mode ();
  6593.    
  6594.    Slrn_Full_Screen_Update = 1;
  6595. }
  6596.  
  6597. /*}}}*/
  6598.  
  6599.  
  6600. /*}}}*/
  6601. /*{{{ cancel_article */
  6602.  
  6603. static void cancel_article (void) /*{{{*/
  6604. {
  6605.    char *from, *msgid, *newsgroups, *dist;
  6606.    char me[256];
  6607.    
  6608.    if (-1 == slrn_check_batch ())
  6609.      return;
  6610.    
  6611.    if (-1 == select_article (0)) return;
  6612.  
  6613.    slrn_update_screen ();
  6614.    
  6615.    if (slrn_get_yesno (0, "Are you sure that you want to cancel this article") <= 0)
  6616.      return;
  6617.    
  6618.    slrn_message_now ("Cancelling...");
  6619.    
  6620.    /* TO cancel, we post a cancel message with a 'control' header.  First, check to
  6621.     * see if this is really the owner of the message.
  6622.     */
  6623.    
  6624.    from = slrn_extract_header ("From: ", 6);
  6625.    if (from != NULL) from = parse_from (from);
  6626.    if (from == NULL) from = "";
  6627.    sprintf (me, "%s@%s", Slrn_User_Info.username, Slrn_User_Info.hostname);
  6628.  
  6629.    if (slrn_case_strcmp ((unsigned char *) from, (unsigned char *) me))
  6630.      {
  6631.     slrn_error ("Failed: Your name: '%s' is not '%s'", me, from);
  6632.     return;
  6633.      }
  6634.    
  6635.    if (NULL == (newsgroups = slrn_extract_header ("Newsgroups: ", 12)))
  6636.      newsgroups = "";
  6637.    
  6638.    if (NULL == (msgid = slrn_extract_header ("Message-ID: ", 12)))
  6639.      {
  6640.     slrn_error ("No message id.");
  6641.     return;
  6642.      }
  6643.    
  6644.    
  6645.    dist = slrn_extract_header("Distribution: ", 14);
  6646.    
  6647.    if (Slrn_Post_Obj->po_start () < 0) return;
  6648.    
  6649.    Slrn_Post_Obj->po_printf ("From: %s\nNewsgroups: %s\nSubject: cancel %s\nControl: cancel %s\n",
  6650.         from, newsgroups, msgid, msgid);
  6651.    
  6652.    if (dist != NULL)
  6653.      {
  6654.     Slrn_Post_Obj->po_printf ("Distribution: %s\n", dist);
  6655.      }
  6656.    
  6657.    Slrn_Post_Obj->po_printf("\nignore\nArticle canceled by slrn %s\n", Slrn_Version);
  6658.    
  6659.    if (0 == Slrn_Post_Obj->po_end ())
  6660.      {
  6661.     slrn_message ("Done.");
  6662.      }
  6663. }
  6664.  
  6665. /*}}}*/
  6666.  
  6667.  
  6668. /*}}}*/
  6669.  
  6670. /*{{{ header/thread (un)deletion/(un)catchup */
  6671. static void delete_header (Slrn_Header_Type *h) /*{{{*/
  6672. {
  6673.    if (h->flags & HEADER_TAGGED) return;
  6674.    if (0 == (h->flags & HEADER_READ))
  6675.      {
  6676.     kill_cross_references (h);
  6677.     h->flags |= HEADER_READ;
  6678.      }
  6679. }
  6680.  
  6681. /*}}}*/
  6682.  
  6683. static void undelete_header (Slrn_Header_Type *h) /*{{{*/
  6684. {
  6685.    if (h->flags & HEADER_TAGGED) return;
  6686.    h->flags &= ~HEADER_READ;
  6687. }
  6688.  
  6689. /*}}}*/
  6690.  
  6691. static void catch_up_all (void) /*{{{*/
  6692. {
  6693.    for_all_headers (delete_header, 1);
  6694. }
  6695.  
  6696. /*}}}*/
  6697.  
  6698. static void un_catch_up_all (void) /*{{{*/
  6699. {
  6700.    for_all_headers (undelete_header, 1);
  6701. }
  6702.  
  6703. /*}}}*/
  6704.  
  6705. static void catch_up_to_here (void) /*{{{*/
  6706. {
  6707.    for_all_headers (delete_header, 0);
  6708. }
  6709.  
  6710. /*}}}*/
  6711.  
  6712. static void un_catch_up_to_here (void) /*{{{*/
  6713. {
  6714.    for_all_headers (undelete_header, 0);
  6715. }
  6716.  
  6717. /*}}}*/
  6718.  
  6719.  
  6720. static void undelete_header_cmd (void) /*{{{*/
  6721. {
  6722.    if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
  6723.        || (Slrn_Current_Header->child == NULL)/* At top with no child */
  6724.        /* or at top with child showing */
  6725.        || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
  6726.      {
  6727.     undelete_header (Slrn_Current_Header);
  6728.      }
  6729.    else
  6730.      {
  6731.     for_this_tree (Slrn_Current_Header, undelete_header);
  6732.      }
  6733.    slrn_header_down_n (1, 0);
  6734.    Slrn_Full_Screen_Update = 1;
  6735. }
  6736.  
  6737. /*}}}*/
  6738.  
  6739. static void delete_header_cmd (void) /*{{{*/
  6740. {
  6741.    if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
  6742.        || (Slrn_Current_Header->child == NULL)/* At top with no child */
  6743.        /* or at top with child showing */
  6744.        || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
  6745.      {
  6746.     delete_header (Slrn_Current_Header);
  6747.      }
  6748.    else
  6749.      {
  6750.     for_this_tree (Slrn_Current_Header, delete_header);
  6751.      }
  6752.    slrn_next_unread_header ();
  6753.    Slrn_Full_Screen_Update = 1;
  6754. }
  6755.  
  6756. /*}}}*/
  6757.  
  6758. static void thread_delete_cmd (void) /*{{{*/
  6759. {
  6760.    for_this_tree (Slrn_Current_Header, delete_header);
  6761.    delete_header_cmd ();
  6762. }
  6763.  
  6764. /*}}}*/
  6765.  
  6766.  
  6767. /*}}}*/
  6768.  
  6769. /*{{{ group_lens functions */
  6770. #if SLRN_HAS_GROUPLENS
  6771. static void grouplens_rate_article (void) /*{{{*/
  6772. {
  6773.    int ch;
  6774.    
  6775.    if ((Slrn_Current_Header == NULL)
  6776.        || (Num_GroupLens_Rated == -1))
  6777.      return;
  6778.    
  6779.    slrn_message_now ("Rate article (1-5):");
  6780.    
  6781.    ch = SLang_getkey ();
  6782.    if ((ch < '1') || (ch > '5'))
  6783.      {
  6784.     slrn_error ("Rating must be in range 1 to 5.");
  6785.     return;
  6786.      }
  6787.    
  6788.    slrn_group_lens_rate_article (Slrn_Current_Header, ch - '0',
  6789.                  (Article_Visible && (Header_Showing == Slrn_Current_Header)));
  6790. }
  6791.  
  6792. /*}}}*/
  6793.  
  6794. #endif
  6795. /*}}}*/
  6796. /*{{{ mouse commands */
  6797.  
  6798. /* actions for different regions:
  6799.  *    - top status line (help)
  6800.  *    - header status line
  6801.  *    - above header status line
  6802.  *    - below header status line
  6803.  *    - bottom status line
  6804.  */
  6805. static void art_mouse (void (*top_status)(void), /*{{{*/
  6806.                void (*header_status)(void),
  6807.                void (*bot_status)(void),
  6808.                void (*normal_region)(void)
  6809.                )
  6810. {
  6811.    int r, c;
  6812.    slrn_get_mouse_rc (&r, &c);
  6813.    
  6814.    /* take top status line into account */
  6815.    if (r == 1)
  6816.      {
  6817.     if (Slrn_Use_Mouse)
  6818.       (void) slrn_execute_menu (c);
  6819.     else if (NULL != top_status) (*top_status) ();
  6820.      return;
  6821.      }
  6822.    
  6823.    if (r >= SLtt_Screen_Rows)
  6824.      return;
  6825.    
  6826.    /* On header status line */
  6827.    if (r - 2 == Header_Window_Nrows)
  6828.      {
  6829.     if (NULL != header_status) (*header_status) ();
  6830.      return;
  6831.      }
  6832.    
  6833.    /* bottom status line */
  6834.    if (r == SLtt_Screen_Rows - 1)
  6835.      {
  6836.     if (NULL != bot_status) (*bot_status) ();
  6837.     return;
  6838.      }
  6839.    
  6840.    if (r - 2 > Header_Window_Nrows)
  6841.      {
  6842.     if (NULL != normal_region) (*normal_region) ();
  6843.      return;
  6844.      }
  6845.    
  6846.    r -= (1 + Last_Cursor_Row);
  6847.    if (r < 0)
  6848.      {
  6849.     r = -r;
  6850.     if (r != (int) slrn_header_up_n (r, 0)) return;
  6851.      }
  6852.    else if (r != (int) slrn_header_down_n (r, 0)) return;
  6853.    
  6854.    select_article (1);
  6855.    /* if (NULL != normal_region) (*normal_region) (); */
  6856.    
  6857. }
  6858.  
  6859. /*}}}*/
  6860.  
  6861.  
  6862. static void art_mouse_left (void) /*{{{*/
  6863. {
  6864.    art_mouse (slrn_article_help, header_pagedn,
  6865.           art_next_unread, art_pagedn);
  6866. }
  6867.  
  6868. /*}}}*/
  6869.  
  6870. static void art_mouse_middle (void) /*{{{*/
  6871. {
  6872.    art_mouse (toggle_header_formats, hide_article,
  6873.           toggle_quotes, hide_article);
  6874. #if 1
  6875.    /* Make up for buggy rxvt which have problems with the middle key. */
  6876.    if (NULL != getenv ("COLORTERM"))
  6877.      {
  6878.     if (SLang_input_pending (7))
  6879.       {
  6880.          while (SLang_input_pending (0)) SLang_getkey ();
  6881.       }
  6882.      }
  6883. #endif
  6884. }
  6885.  
  6886. /*}}}*/
  6887.  
  6888.  
  6889. static void art_mouse_right (void) /*{{{*/
  6890. {
  6891.    art_mouse (slrn_article_help, header_pageup,
  6892.           art_prev_unread, art_pageup);
  6893. }
  6894.  
  6895. /*}}}*/
  6896.  
  6897. /*}}}*/
  6898.  
  6899. /*{{{ slrn_init_article_mode */
  6900.  
  6901. #define A_KEY(s, f)  {s, (int (*)(void)) f}
  6902.  
  6903. static SLKeymap_Function_Type Art_Functions [] = /*{{{*/
  6904. {
  6905. #if SLRN_HAS_GROUPLENS
  6906.    A_KEY("grouplens_rate_article", grouplens_rate_article),
  6907. #endif
  6908.    A_KEY("print_article", print_article_cmd),
  6909.    A_KEY("digit_arg", slrn_digit_arg),
  6910.    A_KEY("browse_url", browse_url),
  6911.    A_KEY("art_xpunge", art_xpunge),
  6912.    A_KEY("wrap_article", toggle_wrap_article),
  6913.    A_KEY("goto_last_read", goto_last_read),
  6914. #if SLRN_HAS_DECODE
  6915.    A_KEY("decode", decode_article),
  6916. #endif
  6917. #if 1
  6918. #if SLRN_HAS_SPOILERS
  6919.      A_KEY("show_spoilers", show_spoilers),
  6920. #endif
  6921. #endif
  6922.      A_KEY("create_score", create_score),
  6923.      A_KEY("toggle_collapse_threads", toggle_collapse_threads),
  6924.      A_KEY("toggle_header_tag", toggle_header_tag),
  6925.      A_KEY("tag_header", num_tag_header),
  6926.      A_KEY("untag_headers", num_untag_headers),
  6927.      A_KEY("repeat_last_key", slrn_repeat_last_key),
  6928.      A_KEY("art_bob", art_bob),
  6929.      A_KEY("art_eob", art_eob),
  6930.      A_KEY("goto_beginning", art_bob),
  6931.      A_KEY("goto_end", art_eob),
  6932.      A_KEY("forward_digest", skip_digest_forward),
  6933.      A_KEY("locate_article", locate_header_by_msgid),
  6934.      A_KEY("delete_thread", thread_delete_cmd),
  6935.      A_KEY("post", slrn_post_cmd),
  6936.      A_KEY("get_children_headers", get_children_headers),
  6937.      A_KEY("get_parent_header", get_parent_header),
  6938.      A_KEY("skip_to_next_group", skip_to_next_group),
  6939.      A_KEY("skip_to_prev_group", skip_to_prev_group),
  6940.      A_KEY("fast_quit", fast_quit),
  6941.      A_KEY("catchup_all", catch_up_all),
  6942.      A_KEY("uncatchup_all", un_catch_up_all),
  6943.      A_KEY("catchup", catch_up_to_here),
  6944.      A_KEY("uncatchup", un_catch_up_to_here),
  6945.      A_KEY("pipe_article", pipe_article),
  6946.      A_KEY("toggle_rot13", toggle_rot13),
  6947.      A_KEY("toggle_show_author", toggle_header_formats),
  6948.      A_KEY("toggle_header_formats", toggle_header_formats),
  6949.      A_KEY("toggle_sort", toggle_sort),
  6950.      A_KEY("skip_quotes", skip_quoted_text),
  6951.      A_KEY("header_bob", header_bob),
  6952.      A_KEY("header_eob", header_eob),
  6953.      A_KEY("goto_article", goto_article),
  6954.      A_KEY("shrink_window", shrink_window),
  6955.      A_KEY("enlarge_window", enlarge_window),
  6956.      A_KEY("scroll_dn", art_pagedn),
  6957.      A_KEY("scroll_up", art_pageup),
  6958.      A_KEY("article_pagedn", art_pagedn),
  6959.      A_KEY("article_pageup", art_pageup),
  6960.      A_KEY("article_linedn", art_linedn),
  6961.      A_KEY("article_lineup", art_lineup),
  6962.      A_KEY("article_search", article_search),
  6963.      A_KEY("author_search_backward", author_search_backward),
  6964.      A_KEY("author_search_forward", author_search_forward),
  6965.      A_KEY("cancel", cancel_article),
  6966.      A_KEY("supersede", supersede),
  6967.      A_KEY("delete", delete_header_cmd),
  6968.      A_KEY("down", header_down),
  6969.      A_KEY("exchange_mark", exchange_mark),
  6970.      A_KEY("followup", followup),
  6971.      A_KEY("forward", forward_article),
  6972.      A_KEY("help", slrn_article_help),
  6973.      A_KEY("hide_article", hide_article),
  6974.      A_KEY("left", art_left),
  6975.      A_KEY("mark_spot", mark_spot),
  6976.      A_KEY("next", art_next_unread),
  6977.      A_KEY("prev", art_prev_unread),
  6978.      A_KEY("quit", art_quit),
  6979.      A_KEY("redraw", slrn_redraw),
  6980.      A_KEY("reply", reply_cmd),
  6981.      A_KEY("right", art_right),
  6982.      A_KEY("save", save_article),
  6983.      A_KEY("subject_search_backward", subject_search_backward),
  6984.      A_KEY("subject_search_forward", subject_search_forward),
  6985.      A_KEY("suspend", art_suspend_cmd),
  6986.      A_KEY("toggle_headers", toggle_headers),
  6987.      A_KEY("toggle_quotes", toggle_quotes),
  6988.      A_KEY("undelete", undelete_header_cmd),
  6989.      A_KEY("up", header_up),
  6990.      A_KEY("pageup", header_pageup),
  6991.      A_KEY("pagedn", header_pagedn),
  6992.      A_KEY("next_same_subject", next_header_same_subject),
  6993.      A_KEY("next_high_score", next_high_score),
  6994.      A_KEY("next_high_score", next_high_score),
  6995.      A_KEY("locate_header_by_msgid", locate_header_by_msgid),
  6996.      A_KEY("post_postponed", slrn_post_postponed),
  6997.      A_KEY("zoom_article_window", zoom_article_window),
  6998.      A_KEY(NULL, NULL)
  6999. };
  7000.  
  7001. /*}}}*/
  7002.  
  7003.  
  7004. static Slrn_Mode_Type Art_Mode_Cap = /*{{{*/
  7005. {
  7006.    NULL,                   /* keymap */
  7007.    art_update_screen,
  7008.    art_winch_sig,               /* sigwinch_fun */
  7009.    slrn_art_hangup,
  7010.    NULL,                   /* enter_mode_hook */
  7011.    SLRN_ARTICLE_MODE,
  7012. };
  7013.  
  7014. /*}}}*/
  7015.  
  7016.  
  7017.  
  7018. void slrn_init_article_mode (void) /*{{{*/
  7019. {
  7020.    char  *err = "Unable to create Article keymap!";
  7021.    char numbuf[2];
  7022.    char ch;
  7023.    
  7024.    if (NULL == (Slrn_Article_Keymap = SLang_create_keymap ("article", NULL)))
  7025.      slrn_exit_error (err);
  7026.    
  7027.    Art_Mode_Cap.keymap = Slrn_Article_Keymap;
  7028.    
  7029.    Slrn_Article_Keymap->functions = Art_Functions;
  7030.    Art_Functions_Ptr = Art_Functions;
  7031.    
  7032.    numbuf[1] = 0;
  7033.    
  7034.    for (ch = '0'; ch <= '9'; ch++)
  7035.      {
  7036.     numbuf[0] = ch;
  7037.     SLkm_define_key (numbuf, (FVOID_STAR) goto_header_number, Slrn_Article_Keymap);
  7038.      }
  7039. #if SLRN_HAS_GROUPLENS
  7040.    numbuf[0] = '0';
  7041.    /* Steal '0' for use as a prefix for rating. */
  7042.    SLkm_define_key  (numbuf, (FVOID_STAR) grouplens_rate_article, Slrn_Article_Keymap);
  7043. #endif
  7044.    
  7045.    SLkm_define_key  ("\033l", (FVOID_STAR) locate_header_by_msgid, Slrn_Article_Keymap);
  7046.    SLkm_define_key ("\0331", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7047.    SLkm_define_key ("\0332", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7048.    SLkm_define_key ("\0333", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7049.    SLkm_define_key ("\0334", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7050.    SLkm_define_key ("\0335", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7051.    SLkm_define_key ("\0336", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7052.    SLkm_define_key ("\0337", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7053.    SLkm_define_key ("\0338", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7054.    SLkm_define_key ("\0339", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7055.    SLkm_define_key ("\0330", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
  7056.    SLkm_define_key  ("*", (FVOID_STAR) toggle_header_tag, Slrn_Article_Keymap);
  7057.    SLkm_define_key  ("#", (FVOID_STAR) num_tag_header, Slrn_Article_Keymap);
  7058.    SLkm_define_key  ("\033#", (FVOID_STAR) num_untag_headers, Slrn_Article_Keymap);
  7059. #if SLRN_HAS_DECODE
  7060.    SLkm_define_key  (":", (FVOID_STAR) decode_article, Slrn_Article_Keymap);
  7061. #endif
  7062.    SLkm_define_key  (" ", (FVOID_STAR) art_pagedn, Slrn_Article_Keymap);
  7063.    SLkm_define_key  ("!", (FVOID_STAR) next_high_score, Slrn_Article_Keymap);
  7064.    SLkm_define_key  (",", (FVOID_STAR) exchange_mark, Slrn_Article_Keymap);
  7065.    SLkm_define_key  (".", (FVOID_STAR) slrn_repeat_last_key, Slrn_Article_Keymap);
  7066.    SLkm_define_key  ("/", (FVOID_STAR) article_search, Slrn_Article_Keymap);
  7067.    SLkm_define_key  (";", (FVOID_STAR) mark_spot, Slrn_Article_Keymap);
  7068.    SLkm_define_key  ("<", (FVOID_STAR) art_bob, Slrn_Article_Keymap);
  7069.    SLkm_define_key  ("=", (FVOID_STAR) next_header_same_subject, Slrn_Article_Keymap);
  7070.    SLkm_define_key  (">", (FVOID_STAR) art_eob, Slrn_Article_Keymap);
  7071.    SLkm_define_key  ("?", (FVOID_STAR) slrn_article_help, Slrn_Article_Keymap);
  7072.    SLkm_define_key  ("A", (FVOID_STAR) author_search_backward, Slrn_Article_Keymap);
  7073.    SLkm_define_key  ("F", (FVOID_STAR) forward_article, Slrn_Article_Keymap);
  7074.    SLkm_define_key  ("H", (FVOID_STAR) hide_article, Slrn_Article_Keymap);
  7075.    SLkm_define_key  ("K", (FVOID_STAR) create_score, Slrn_Article_Keymap);
  7076.    SLkm_define_key  ("L", (FVOID_STAR) goto_last_read, Slrn_Article_Keymap);
  7077.    SLkm_define_key  ("N", (FVOID_STAR) skip_to_next_group, Slrn_Article_Keymap);
  7078.    SLkm_define_key  ("P", (FVOID_STAR) slrn_post_cmd, Slrn_Article_Keymap);
  7079.    SLkm_define_key  ("S", (FVOID_STAR) subject_search_backward, Slrn_Article_Keymap);
  7080.    SLkm_define_key  ("T", (FVOID_STAR) toggle_quotes, Slrn_Article_Keymap);
  7081.    SLkm_define_key  ("U", (FVOID_STAR) browse_url, Slrn_Article_Keymap);
  7082.    SLkm_define_key  ("W", (FVOID_STAR) toggle_wrap_article, Slrn_Article_Keymap);
  7083.    SLkm_define_key  ("\033^C", (FVOID_STAR) cancel_article, Slrn_Article_Keymap);
  7084.    SLkm_define_key  ("\033^P", (FVOID_STAR) get_children_headers, Slrn_Article_Keymap);
  7085.    SLkm_define_key  ("\033^S", (FVOID_STAR) supersede, Slrn_Article_Keymap);
  7086.    SLkm_define_key  ("\033a", (FVOID_STAR) toggle_header_formats, Slrn_Article_Keymap);
  7087.    SLkm_define_key  ("\033d", (FVOID_STAR) thread_delete_cmd, Slrn_Article_Keymap);
  7088.    SLkm_define_key  ("\033p", (FVOID_STAR) get_parent_header, Slrn_Article_Keymap);
  7089.    SLkm_define_key  ("\033t", (FVOID_STAR) toggle_collapse_threads, Slrn_Article_Keymap);
  7090.    SLkm_define_key  ("\r", (FVOID_STAR) art_pagedn, Slrn_Article_Keymap);
  7091.    SLkm_define_key  ("\t", (FVOID_STAR) skip_quoted_text, Slrn_Article_Keymap);
  7092.    SLkm_define_key  ("^L", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
  7093.    SLkm_define_key  ("^M", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
  7094.    SLkm_define_key  ("^P", (FVOID_STAR) header_up, Slrn_Article_Keymap);
  7095.    SLkm_define_key  ("^R", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
  7096.    SLkm_define_key  ("^Z", (FVOID_STAR) art_suspend_cmd, Slrn_Article_Keymap);
  7097.    SLkm_define_key  ("a", (FVOID_STAR) author_search_forward, Slrn_Article_Keymap);
  7098.    SLkm_define_key  ("b", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
  7099.    SLkm_define_key  ("d", (FVOID_STAR) delete_header_cmd, Slrn_Article_Keymap);
  7100.    SLkm_define_key  ("f", (FVOID_STAR) followup, Slrn_Article_Keymap);
  7101.    SLkm_define_key  ("g", (FVOID_STAR) skip_digest_forward, Slrn_Article_Keymap);
  7102.    SLkm_define_key  ("j", (FVOID_STAR) goto_article, Slrn_Article_Keymap);
  7103.    SLkm_define_key  ("n", (FVOID_STAR) art_next_unread, Slrn_Article_Keymap);
  7104.    SLkm_define_key  ("o", (FVOID_STAR) save_article, Slrn_Article_Keymap);
  7105.    SLkm_define_key  ("p", (FVOID_STAR) art_prev_unread, Slrn_Article_Keymap);
  7106.    SLkm_define_key  ("q", (FVOID_STAR) art_quit, Slrn_Article_Keymap);
  7107.    SLkm_define_key  ("r", (FVOID_STAR) reply_cmd, Slrn_Article_Keymap);
  7108.    SLkm_define_key  ("s", (FVOID_STAR) subject_search_forward, Slrn_Article_Keymap);
  7109.    SLkm_define_key  ("t", (FVOID_STAR) toggle_headers, Slrn_Article_Keymap);
  7110.    SLkm_define_key  ("u", (FVOID_STAR) undelete_header_cmd, Slrn_Article_Keymap);
  7111.    SLkm_define_key  ("x", (FVOID_STAR) art_xpunge, Slrn_Article_Keymap);
  7112.    SLkm_define_key  ("y", (FVOID_STAR) print_article_cmd, Slrn_Article_Keymap);
  7113.    SLkm_define_key  ("|", (FVOID_STAR) pipe_article, Slrn_Article_Keymap);
  7114. #if defined(IBMPC_SYSTEM)
  7115.    SLkm_define_key  ("^@S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
  7116.    SLkm_define_key  ("\xE0S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
  7117. #else
  7118.    SLkm_define_key  ("^?", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
  7119. #endif
  7120. #if defined(IBMPC_SYSTEM)
  7121.    SLkm_define_key  ("\033^@H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
  7122.    SLkm_define_key  ("\033\xE0H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
  7123.    SLkm_define_key  ("\033^@P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
  7124.    SLkm_define_key  ("\033\xE0P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
  7125.    SLkm_define_key  ("^@H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
  7126.    SLkm_define_key  ("\xE0H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
  7127.    SLkm_define_key  ("^@P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
  7128.    SLkm_define_key  ("\xE0P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
  7129.    SLkm_define_key  ("^@M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
  7130.    SLkm_define_key  ("\xE0M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
  7131.    SLkm_define_key  ("^@K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
  7132.    SLkm_define_key  ("\xE0K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
  7133. #else
  7134.    SLkm_define_key  ("\033\033[A", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
  7135.    SLkm_define_key  ("\033\033OA", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
  7136.    SLkm_define_key  ("\033\033[B", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
  7137.    SLkm_define_key  ("\033\033OB", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
  7138.    SLkm_define_key  ("\033[A", (FVOID_STAR) header_up, Slrn_Article_Keymap);
  7139.    SLkm_define_key  ("\033OA", (FVOID_STAR) header_up, Slrn_Article_Keymap);
  7140.    SLkm_define_key  ("\033[B", (FVOID_STAR) header_down, Slrn_Article_Keymap);
  7141.    SLkm_define_key  ("\033OB", (FVOID_STAR) header_down, Slrn_Article_Keymap);
  7142.    SLkm_define_key  ("\033[C", (FVOID_STAR) art_right, Slrn_Article_Keymap);
  7143.    SLkm_define_key  ("\033OC", (FVOID_STAR) art_right, Slrn_Article_Keymap);
  7144.    SLkm_define_key  ("\033[D", (FVOID_STAR) art_left, Slrn_Article_Keymap);
  7145.    SLkm_define_key  ("\033OD", (FVOID_STAR) art_left, Slrn_Article_Keymap);
  7146. #endif
  7147.    SLkm_define_key  ("\033S", (FVOID_STAR) toggle_sort, Slrn_Article_Keymap);
  7148.    SLkm_define_key  ("^U", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
  7149.    SLkm_define_key  ("\033V", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
  7150. #if defined(IBMPC_SYSTEM)
  7151.    SLkm_define_key  ("^@I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
  7152.    SLkm_define_key  ("\xE0I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
  7153.    SLkm_define_key  ("^@Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
  7154.    SLkm_define_key  ("\xE0Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
  7155. #else
  7156.    SLkm_define_key  ("\033[5~", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
  7157.    SLkm_define_key  ("\033[6~", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
  7158. #endif
  7159.    SLkm_define_key  ("^D", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
  7160.    SLkm_define_key  ("^V", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
  7161.    SLkm_define_key  ("\033>", (FVOID_STAR) header_eob, Slrn_Article_Keymap);
  7162.    SLkm_define_key  ("\033<", (FVOID_STAR) header_bob, Slrn_Article_Keymap);
  7163.    SLkm_define_key  ("c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
  7164.    SLkm_define_key  ("\033c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
  7165.    SLkm_define_key  ("\033u", (FVOID_STAR) un_catch_up_all, Slrn_Article_Keymap);
  7166.    SLkm_define_key  ("\033C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
  7167.    SLkm_define_key  ("C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
  7168.    SLkm_define_key  ("\033U", (FVOID_STAR) un_catch_up_to_here, Slrn_Article_Keymap);
  7169.    SLkm_define_key  ("\033R", (FVOID_STAR) toggle_rot13, Slrn_Article_Keymap);
  7170. #if 1
  7171. #if SLRN_HAS_SPOILERS
  7172.    SLkm_define_key  ("\033?", (FVOID_STAR) show_spoilers, Slrn_Article_Keymap);
  7173. #endif
  7174. #endif
  7175.    SLkm_define_key  ("^N", (FVOID_STAR) header_down, Slrn_Article_Keymap);
  7176.    SLkm_define_key  ("^", (FVOID_STAR) enlarge_window, Slrn_Article_Keymap);
  7177.    SLkm_define_key  ("^^", (FVOID_STAR) shrink_window, Slrn_Article_Keymap);
  7178.    SLkm_define_key  ("\033P", (FVOID_STAR) slrn_post_postponed, Slrn_Article_Keymap);
  7179.    
  7180.    /* mouse (left/middle/right) */
  7181.    SLkm_define_key  ("\033[M\040", (FVOID_STAR) art_mouse_left, Slrn_Article_Keymap);
  7182.    SLkm_define_key  ("\033[M\041", (FVOID_STAR) art_mouse_middle, Slrn_Article_Keymap);
  7183.    SLkm_define_key  ("\033[M\042", (FVOID_STAR) art_mouse_right, Slrn_Article_Keymap);
  7184.  
  7185.    SLkm_define_key  ("z", (FVOID_STAR) zoom_article_window, Slrn_Article_Keymap);
  7186.    
  7187.    if (SLang_Error) slrn_exit_error (err);
  7188. }
  7189.  
  7190. /*}}}*/
  7191.  
  7192.  
  7193. /*}}}*/
  7194. /*{{{ slrn_article_mode and support functions */
  7195.  
  7196.  
  7197. static void slrn_art_hangup (int sig) /*{{{*/
  7198. {
  7199.    (void) sig;
  7200.    if (Slrn_Current_Header != NULL)
  7201.      undelete_header_cmd ();               /* in case we are reading one */
  7202.    art_quit ();
  7203. }
  7204.  
  7205. /*}}}*/
  7206.  
  7207. static void mark_ranges_read (Slrn_Range_Type *r) /*{{{*/
  7208. {
  7209.    Slrn_Header_Type *h = Slrn_First_Header;
  7210.    int min, max;
  7211.    
  7212.    while ((r != NULL) && (h != NULL))
  7213.      {
  7214.     min = r->min;
  7215.     max = r->max;
  7216.     while (h != NULL)
  7217.       {
  7218.          if (h->number < min)
  7219.            {
  7220.           h = h->real_next;
  7221.           continue;
  7222.            }
  7223.          
  7224.          if (h->number > max)
  7225.            {
  7226.           break;
  7227.            }
  7228.          
  7229.          h->flags |= HEADER_READ;
  7230.          h = h->real_next;
  7231.       }
  7232.     r = r->next;
  7233.      }
  7234. }
  7235.  
  7236. /*}}}*/
  7237.  
  7238. static void init_graphic_chars (void)
  7239. {
  7240. #ifndef IBMPC_SYSTEM
  7241.    if (SLtt_Has_Alt_Charset == 0)
  7242.      Slrn_Simulate_Graphic_Chars = 1;
  7243. #endif
  7244.  
  7245.    if (Slrn_Simulate_Graphic_Chars)
  7246.      {
  7247.     Graphic_LTee_Char = '+';
  7248.     Graphic_UTee_Char = '+';
  7249.     Graphic_LLCorn_Char = '\\';
  7250.     Graphic_HLine_Char = '-';
  7251.     Graphic_VLine_Char = '|';
  7252.     Graphic_ULCorn_Char = '/';
  7253.     
  7254.     Graphic_Chars_Mode = SIMULATED_CHAR_SET_MODE;
  7255.     return;
  7256.      }
  7257.    
  7258.    Graphic_Chars_Mode = ALT_CHAR_SET_MODE;
  7259.    Graphic_LTee_Char = SLSMG_LTEE_CHAR;
  7260.    Graphic_UTee_Char = SLSMG_UTEE_CHAR;
  7261.    Graphic_LLCorn_Char = SLSMG_LLCORN_CHAR;
  7262.    Graphic_HLine_Char = SLSMG_HLINE_CHAR;
  7263.    Graphic_VLine_Char = SLSMG_VLINE_CHAR;
  7264.    Graphic_ULCorn_Char = SLSMG_ULCORN_CHAR;
  7265.  
  7266.    Graphic_Chars_Mode = ALT_CHAR_SET_MODE;
  7267. }
  7268.  
  7269. /* If all > 0, get last 'all' headers from server independent of whether
  7270.  *             they have been read or not.
  7271.  * If all < 0, and this is not the first time this group has been accessed,
  7272.  *             either during this session or during previous sessions, get
  7273.  *             that last 'all' UNREAD articles.
  7274.  * Otherwise,  fetch ALL UNREAD headers from the server.
  7275.  */
  7276. int slrn_select_article_mode (Slrn_Group_Type *g, int all, int score) /*{{{*/
  7277. {
  7278.    int min, max;
  7279.    int smin, smax;
  7280.    Slrn_Range_Type *r;
  7281.    int status;
  7282.  
  7283.    init_graphic_chars ();
  7284.  
  7285.    Header_Window_HScroll = 0;
  7286.    User_Aborted_Group_Read = 0;
  7287.    Headers = Slrn_First_Header = NULL;
  7288.    Threads_Collapsed = 0;
  7289.    Same_Subject_Start_Header = NULL;
  7290.    Number_Killed = Number_High_Scored = Number_Low_Scored = 0;
  7291.    
  7292.    Current_Group = g;
  7293.    r = &g->range;
  7294.    Slrn_Current_Group_Name = g->name;
  7295.    
  7296.    if (Slrn_Server_Obj->sv_reset_has_xover)
  7297.      Slrn_Server_Obj->sv_has_xover = 1;
  7298.    
  7299.    Slrn_Score_After_XOver = Slrn_Server_Obj->sv_has_xover;
  7300.  
  7301. #if SLRN_HAS_SLANG
  7302.    SLang_run_hooks ("pre_article_mode_hook", 0);
  7303.    if (SLang_Error)
  7304.      return -1;
  7305. #endif
  7306.    
  7307.    if (score && (1 == slrn_open_score (Slrn_Current_Group_Name)))
  7308.      Perform_Scoring = 1;
  7309.    else Perform_Scoring = 0;
  7310.    
  7311.    Slrn_Server_Min = r->min;
  7312.    Slrn_Server_Max = r->max;
  7313.    r = r->next;
  7314.    /* Now r points to ranges already read.  */
  7315.    
  7316.    status = 0;
  7317.    
  7318.    if (all > 0)
  7319.      {
  7320.     min = Slrn_Server_Max - all + 1;
  7321.     if (min < Slrn_Server_Min) min = Slrn_Server_Min;
  7322.     status = get_headers (min, Slrn_Server_Max, &all);
  7323.     if (status != -1)
  7324.       mark_ranges_read (r);
  7325.      }
  7326.    else
  7327.      {
  7328.     if ((all < 0) && (r != NULL))
  7329.       {
  7330.          int unread;
  7331.  
  7332.          /* This condition will occur when the user wants to read unread
  7333.           * articles that occur in a gap, i.e., RRRUUUUURRRUUUUUUU and
  7334.           * we need to dig back far enough below the last group of read
  7335.           * ones until we have retrieved abs(all) articles.
  7336.           * 
  7337.           * The problem with this is that some articles may not be 
  7338.           * available on the server which means that the number to
  7339.           * go back will be under estimated.
  7340.           */
  7341.          all = -all;
  7342.          
  7343.          while (r->next != NULL) r = r->next;
  7344.          /* Go back through previously read articles counting unread.
  7345.           * If number unread becomes greater than the number that we
  7346.           * intend to read, then we know where to start querying 
  7347.           * the server.
  7348.           */
  7349.          unread = 0;
  7350.          max = Slrn_Server_Max;
  7351.          while (r->prev != NULL)
  7352.            {
  7353.           unread += max - r->max;
  7354.           if (unread >= all) break;
  7355.           max = r->min - 1;
  7356.           r = r->prev;
  7357.            }
  7358.          
  7359.          if (unread >= all)
  7360.            {
  7361.           /* This may be problematic if some articles are missing on 
  7362.            * the server.  If that is the case, smin will be to high
  7363.            * and we will fall short of the goal.
  7364.            */
  7365.           smin = r->max + (unread - all) + 1;
  7366.            }
  7367.          else smin = Slrn_Server_Min;
  7368.          smax = Slrn_Server_Max;
  7369.          r = r->next;
  7370.       }
  7371.     else
  7372.       {
  7373.          /* all == 0, or no previously read articles. */
  7374.          smin = Slrn_Server_Min;
  7375.          smax = Slrn_Server_Max;
  7376.          if (r != NULL)
  7377.            {
  7378.           Slrn_Range_Type *r1;
  7379.           
  7380.           /* Estimate how many are available to read */
  7381.           all = smax - r->max;
  7382. #if 0                       /* is this correct?? */
  7383.           all++;
  7384. #endif
  7385.           
  7386.           /* Now subtract the ones that we have already read. */
  7387.           r1 = r->next;
  7388.           while (r1 != NULL)
  7389.             {
  7390.                all -= (r1->max - r1->min) + 1;
  7391.                r1 = r1->next;
  7392.             }
  7393.           /* This condition should never arise */
  7394.           if (all == 0) all = smax - smin + 1;
  7395.            }
  7396.          else all = smax - smin + 1;
  7397.       }
  7398.     
  7399.     while (r != NULL)
  7400.       {
  7401.          if (r->min > smin)
  7402.            {
  7403.           min = smin;
  7404.           max = r->min - 1;
  7405.  
  7406.           status = get_headers (min, max, &all);
  7407.  
  7408.           if (status == -1)
  7409.             break;
  7410.           
  7411.           if (status == 0)
  7412.             {
  7413.                Slrn_Groups_Dirty = 1;
  7414.                r->min = min;
  7415.             }
  7416.           
  7417.           smin = r->max + 1;
  7418.            }
  7419.          else
  7420.            {
  7421.           smin = r->max + 1;
  7422.            }
  7423.          r = r->next;
  7424.       }
  7425.     
  7426.     if (smin <= smax)
  7427.       {
  7428.          status = get_headers (smin, smax, &all);
  7429.       }
  7430.      }
  7431.    
  7432.    if ((status == -1) || SLKeyBoard_Quit)
  7433.      {
  7434.     if (SLang_Error == USER_BREAK)
  7435.       slrn_error_now (0, "Group transfer aborted.");
  7436.     else
  7437.       slrn_error_now (0, "Server read failed.");
  7438.     
  7439.     /* This means that we cannot update ranges for this group because 
  7440.      * the user aborted and update_ranges assumes that all articles 
  7441.      * upto server max are present.
  7442.      */
  7443.     User_Aborted_Group_Read = 1;
  7444.     art_quit ();
  7445.      }
  7446.    else if (Slrn_Score_After_XOver 
  7447.        && Perform_Scoring)
  7448.      score_headers ();
  7449.    
  7450.    if (Headers == NULL)
  7451.      {
  7452.     slrn_close_score ();
  7453.     if (Number_Killed)
  7454.       slrn_error ("No unread articles found. (%d killed)", Number_Killed);
  7455.     else
  7456.       slrn_error ("No unread articles found.");
  7457.  
  7458.     free_kill_lists_and_update ();
  7459.     Slrn_Current_Group_Name = NULL;
  7460.     if (SLang_Error == USER_BREAK) return -1;
  7461.     else return -2;
  7462.      }
  7463.    
  7464.    make_hash_table ();
  7465.    
  7466.    /* This must go here to fix up the next/prev pointers */
  7467.    sort_by_server_number ();
  7468.  
  7469.    extract_real_names ();
  7470.  
  7471.    slrn_chmap_fix_headers ();
  7472.  
  7473.    slrn_push_mode (&Art_Mode_Cap);
  7474.    
  7475.    Slrn_Current_Header = Headers;
  7476.    Last_Cursor_Row = 0;
  7477.    Mark_Header = NULL;
  7478.    init_header_window_struct ();
  7479.    
  7480.    Article_Current_Line = Slrn_Article_Lines = NULL;
  7481.    At_End_Of_Article = NULL;
  7482.    Header_Showing = NULL;
  7483.    SLMEMSET ((char *) &Slrn_Article_Window, 0, sizeof (SLscroll_Window_Type));
  7484.    
  7485.    set_article_visibility (0);
  7486.  
  7487. #if SLRN_HAS_GROUPLENS
  7488.    /* slrn_set_suspension (1); */
  7489.    Num_GroupLens_Rated = slrn_get_grouplens_scores ();
  7490.    /* slrn_set_suspension (0); */
  7491. #endif
  7492.  
  7493. #if SLRN_HAS_SLANG
  7494.    (void) SLang_run_hooks ("article_mode_hook", 0);
  7495. #endif   
  7496.    sort_by_sorting_mode ();
  7497.    header_bob ();
  7498.  
  7499.    quick_help ();
  7500.    
  7501.    if (Slrn_Startup_With_Article) art_pagedn ();
  7502.    
  7503.    if (SLang_Error == 0)
  7504.      {
  7505.     if (Perform_Scoring 
  7506.         /* && (Number_Killed || Number_High_Scored) */
  7507.         )
  7508.       {
  7509. #if SLRN_HAS_GROUPLENS
  7510.          if (Num_GroupLens_Rated != -1)
  7511.            {
  7512.           slrn_message ("Num Killed: %u, Num High: %u, Num Low: %u, Num GroupLens Rated: %d",
  7513.                 Number_Killed, Number_High_Scored, Number_Low_Scored,
  7514.                 Num_GroupLens_Rated);
  7515.            }
  7516.          else
  7517. #endif
  7518.            slrn_message ("Num Killed: %u, Num High: %u, Num Low: %u",
  7519.                  Number_Killed, Number_High_Scored, Number_Low_Scored);
  7520.       }
  7521.     
  7522.     else slrn_clear_message ();
  7523.      }
  7524.    
  7525.    Number_Low_Scored = Number_Killed = Number_High_Scored = 0;
  7526.    return 0;
  7527. }
  7528.  
  7529. /*}}}*/
  7530.  
  7531. /*}}}*/
  7532.  
  7533. /*{{{ screen update functions */
  7534.  
  7535. static char *Header_Display_Formats [SLRN_MAX_HEADER_FORMATS];
  7536. static unsigned int Header_Format_Number;
  7537.  
  7538. int slrn_set_header_format (unsigned int num, char *fmt)
  7539. {
  7540.    if (num >= SLRN_MAX_HEADER_FORMATS)
  7541.      return -1;
  7542.    
  7543.    if (Header_Display_Formats[num] != NULL)
  7544.      SLang_free_slstring (Header_Display_Formats[num]);
  7545.  
  7546.    if ((fmt == NULL) || (*fmt == 0))
  7547.      {
  7548.     Header_Display_Formats[num] = NULL;
  7549.     return 0;
  7550.      }
  7551.  
  7552.    if (NULL == (Header_Display_Formats[num] = SLang_create_slstring (fmt)))
  7553.      return -1;
  7554.    
  7555.    return 0;
  7556. }
  7557.      
  7558. static void toggle_header_formats (void)
  7559. {
  7560.    if (Slrn_Prefix_Arg_Ptr != NULL)
  7561.      {
  7562.     Header_Format_Number = (unsigned int) *Slrn_Prefix_Arg_Ptr;
  7563.     Slrn_Prefix_Arg_Ptr = NULL;
  7564.      }
  7565.    else Header_Format_Number++;
  7566.  
  7567.    Header_Format_Number = Header_Format_Number % SLRN_MAX_HEADER_FORMATS;
  7568.    
  7569.    if (Header_Display_Formats[Header_Format_Number] == NULL)
  7570.      Header_Format_Number = 0;
  7571.  
  7572.    Slrn_Full_Screen_Update = 1;
  7573. }
  7574.  
  7575.  
  7576.  
  7577. /* Wrappers around SLsmg routines. */
  7578. static void smg_write_nchars (char *s, unsigned int n)
  7579. {
  7580.    unsigned char *s1, *smax;
  7581.    unsigned int eight_bit;
  7582.  
  7583.    s1 = (unsigned char *) s;
  7584.    smax = s1 + n;
  7585.    eight_bit = SLsmg_Display_Eight_Bit;
  7586.    
  7587.    while (s1 < smax)
  7588.      {
  7589.     if ((*s1 & 0x80) && (eight_bit > (unsigned int) *s1))
  7590.       {
  7591.          if (s != (char *) s1)
  7592.            SLsmg_write_nchars (s, (unsigned int) ((char *)s1 - s));
  7593.          SLsmg_write_char ('?');
  7594.          s1++;
  7595.          s = (char *) s1;
  7596.       }
  7597.     else s1++;
  7598.      }
  7599.    
  7600.    if (s != (char *)s1)
  7601.      SLsmg_write_nchars (s, (unsigned int) ((char *)s1 - s));
  7602. }
  7603.  
  7604. static void smg_write_string (char *s)
  7605. {
  7606.    smg_write_nchars (s, strlen (s));
  7607. }
  7608.  
  7609. static void smg_write_char (char c)
  7610. {
  7611.    smg_write_nchars (&c, 1);
  7612. }
  7613.  
  7614. static void quick_help (void) /*{{{*/
  7615. {
  7616.    char *msg;
  7617.    
  7618.    if (Slrn_Batch) return;
  7619.  
  7620.    if (Article_Visible == 0)
  7621.      {
  7622.     msg = "SPC:Select  Ctrl-D:PgDn  Ctrl-U:PgUp  d:Mark-as-Read  n:Next  p:Prev  q:Quit";
  7623.     if (Slrn_Header_Help_Line != NULL) msg = Slrn_Header_Help_Line;
  7624.      }
  7625.    else
  7626.      {
  7627.     msg = "SPC:Pgdn  B:PgUp  u:Un-Mark-as-Read  f:Followup  n:Next  p:Prev  q:Quit";
  7628.     if (Slrn_Art_Help_Line != NULL) msg = Slrn_Art_Help_Line;
  7629.      }
  7630.  
  7631.    if (0 == slrn_message (msg))
  7632.      Slrn_Message_Present = 0;
  7633. }
  7634.  
  7635. /*}}}*/
  7636.  
  7637. static void write_rot13 (unsigned char *buf) /*{{{*/
  7638. {
  7639.    static int init_rot13;
  7640.    static char rot13buf[256];
  7641.    unsigned char ch;
  7642.    int i;
  7643.    
  7644.    if (init_rot13 == 0)
  7645.      {
  7646.     init_rot13 = 1;
  7647.     for (i = 0; i < 256; i++)
  7648.       {
  7649.          rot13buf[i] = i;
  7650.       }
  7651.     
  7652.     for (i = 'A'; i <= 'M'; i++)
  7653.       {
  7654.          rot13buf[i] = i + 13;
  7655.          /* Now take care of lower case ones */
  7656.          rot13buf[i + 32] =  i + 32 + 13;
  7657.       }
  7658.     
  7659.     for (i = 'N'; i <= 'Z'; i++)
  7660.       {
  7661.          rot13buf[i] = i - 13;
  7662.          /* Now take care of lower case ones */
  7663.          rot13buf[i + 32] =  i + 32 - 13;
  7664.       }
  7665.      }
  7666.    
  7667.    while ((ch = *buf++) != 0)
  7668.      {
  7669.     ch = rot13buf[ch];
  7670.     smg_write_nchars ((char *) &ch, 1);
  7671.      }
  7672. }
  7673.  
  7674. /*}}}*/
  7675.  
  7676. /*{{{ utility routines */
  7677.  
  7678. #if SLRN_HAS_SPOILERS
  7679. /* write out the line, replacing all printable chars with '*' */
  7680. static void write_spoiler (unsigned char *buf) /*{{{*/
  7681. {
  7682.    unsigned char ch;
  7683.    
  7684.    Spoilers_Visible = Slrn_Current_Header;
  7685.    
  7686.    if (Slrn_Spoiler_Char == ' ')
  7687.      return;
  7688.    
  7689.    while ((ch = *buf++) != 0)
  7690.      {
  7691.     if (!isspace(ch)) ch = Slrn_Spoiler_Char;
  7692.     smg_write_nchars ((char *) &ch, 1);
  7693.      }
  7694. }
  7695. /*}}}*/
  7696. #endif
  7697.  
  7698. static void draw_tree (Slrn_Header_Type *h) /*{{{*/
  7699. {
  7700.    unsigned char buf[2];
  7701.  
  7702. #if !defined(IBMPC_SYSTEM)
  7703.    if (Graphic_Chars_Mode == 0)
  7704.      {
  7705.     smg_write_string ((char *) h->tree);
  7706.     smg_write_string ("  ");
  7707.     return;
  7708.      }
  7709.    if (Graphic_Chars_Mode == ALT_CHAR_SET_MODE)
  7710.      SLsmg_set_char_set (1);
  7711. #endif
  7712.    
  7713.    slrn_set_color (TREE_COLOR);
  7714.    
  7715.    if (*h->tree) smg_write_string ((char *) h->tree);
  7716.    
  7717.    if (h->flags & FAKE_CHILDREN)
  7718.      {
  7719.     buf[0] = Graphic_UTee_Char;
  7720.     buf[1] = Graphic_HLine_Char;
  7721.     SLsmg_forward (-1);
  7722.     smg_write_char (Graphic_ULCorn_Char);
  7723.      }
  7724.    else if ((h->sister == NULL) ||
  7725.         ((h->sister->flags & FAKE_PARENT) && ((h->flags & FAKE_PARENT) == 0)))
  7726.      {
  7727.     buf[0] = Graphic_LLCorn_Char;
  7728.     buf[1] = Graphic_HLine_Char;
  7729.      }
  7730.    else
  7731.      {
  7732.     buf[0] = Graphic_LTee_Char;
  7733.     buf[1] = Graphic_HLine_Char;
  7734.      }
  7735.    smg_write_nchars ((char *) buf, 2);
  7736.  
  7737. #if !defined(IBMPC_SYSTEM)
  7738.    if (Graphic_Chars_Mode == ALT_CHAR_SET_MODE) SLsmg_set_char_set (0);
  7739. #endif
  7740. }
  7741.  
  7742. /*}}}*/
  7743.  
  7744. /*{{{ check_subject */
  7745.  
  7746. /*
  7747.  * This checks if subjects should be printed (correctly I hope)
  7748.  * hacked: articles in a tree shouldn't display their subject if the
  7749.  *          subject is already displayed (i.e. at top)
  7750.  * To add: take more Re:'s into account (currently, one is allowed)
  7751.  */
  7752. int Slrn_Show_Thread_Subject = 0;
  7753. static int check_subject (Slrn_Header_Type *h) /*{{{*/
  7754. {
  7755.    char *psubj, *subj;
  7756.    
  7757.    subj = h->subject;
  7758.    psubj = h->prev->subject;           /* used to be: h->parent->subject */
  7759.    if ((subj == NULL) || (psubj == NULL)) return 1;
  7760.  
  7761.    return subject_cmp ((unsigned char *)subj, (unsigned char *)psubj);
  7762. }
  7763.  
  7764. /*}}}*/
  7765.  
  7766. /*}}}*/
  7767.  
  7768. #if SLRN_HAS_END_OF_THREAD
  7769. static int display_end_of_thread (Slrn_Header_Type *h) /*{{{*/
  7770. {
  7771.    Slrn_Header_Type *parent, *next_parent;
  7772.    
  7773.    if ((h == NULL)
  7774.        || (h->parent == NULL)
  7775.        || (h->child != NULL)
  7776.        || (h->next == NULL))
  7777.      return -1;
  7778.    
  7779.    parent = h->parent;
  7780.    while (parent->parent != NULL) parent = parent->parent;
  7781.          
  7782.    next_parent = h->next;
  7783.    while (next_parent->parent != NULL) 
  7784.      next_parent = next_parent->parent;
  7785.    
  7786.    if (next_parent != parent)
  7787.      {
  7788.     if ((Header_Window_Nrows == 0)
  7789.         && (next_parent != NULL)
  7790.         && (next_parent->subject != NULL))
  7791.       slrn_message ("End of Thread.  Next: %s", next_parent->subject);
  7792.     else
  7793.       slrn_message ("End of Thread.");
  7794.     return 0;
  7795.      }
  7796.    
  7797.    if ((h->sister == NULL) 
  7798.        || (h->parent != h->next->parent)
  7799.        || (FAKE_PARENT & (h->next->flags ^ h->flags)))
  7800.      {
  7801.     /* The last test involving ^ is necessary because the two can be
  7802.      * sisters except that one can have a fake parent.  If this is the
  7803.      * case, we are at the end of a subthread.
  7804.      */
  7805.     slrn_message ("End of Sub-Thread");
  7806.     return 0;
  7807.      }
  7808.    
  7809.    return -1;
  7810. }
  7811. /*}}}*/
  7812. #endif
  7813.  
  7814.  
  7815. static void disp_write_flags (Slrn_Header_Type *h, int row)
  7816. {
  7817.    unsigned int flags = h->flags;
  7818.  
  7819.    /* Do not write header numbers if we are displaying at bottom of the
  7820.     * screen in the display area.  When this happens, the header window is
  7821.     * not visible.
  7822.     */
  7823.    if (row + 1 != SLtt_Screen_Rows)
  7824.      {
  7825.     if (Slrn_Use_Header_Numbers)
  7826.       {
  7827.          slrn_set_color (HEADER_NUMBER_COLOR);
  7828.          SLsmg_printf ("%2d", row);
  7829.          if (row > Largest_Header_Number) Largest_Header_Number = row;
  7830.       }
  7831.     else smg_write_string ("  ");
  7832.      }
  7833.    
  7834.  
  7835.    if (flags & HEADER_NTAGGED)
  7836.      {
  7837.     slrn_set_color (HIGH_SCORE_COLOR);
  7838.     SLsmg_printf ("%2d",
  7839.               h->tag_number);
  7840.      }
  7841.    else
  7842.      {
  7843.     if ((flags & HEADER_HIGH_SCORE)
  7844.         || ((flags & FAKE_HEADER_HIGH_SCORE)
  7845.         && (h->child != NULL)
  7846.         && (h->child->flags & HEADER_HIDDEN)))
  7847.       {
  7848.          slrn_set_color (HIGH_SCORE_COLOR);
  7849.          SLsmg_printf ("!%c",
  7850.                ((flags & HEADER_TAGGED) ? '*': ' '));
  7851.       }
  7852.     else
  7853.       {
  7854.          slrn_set_color (0);
  7855.          SLsmg_printf (" %c",
  7856.                ((flags & HEADER_TAGGED) ? '*': ' '));
  7857.       }
  7858.      }
  7859.    slrn_set_color (0);
  7860.    SLsmg_write_char ((flags & HEADER_READ) ? 'D': '-');
  7861. }
  7862.    
  7863. #if SLRN_HAS_GROUPLENS
  7864. # define SLRN_GROUPLENS_DISPLAY_WIDTH 5
  7865. static void disp_write_grplens (Slrn_Header_Type *h)
  7866. {
  7867.    char buf [SLRN_GROUPLENS_DISPLAY_WIDTH], *b, *bmax;
  7868.  
  7869.    if (Num_GroupLens_Rated == -1)
  7870.      return;
  7871.  
  7872.    b = buf;
  7873.    bmax = b + SLRN_GROUPLENS_DISPLAY_WIDTH;   
  7874.    while (b < bmax) *b++ = ' ';
  7875.      {
  7876.     int pred = h->gl_pred;
  7877.  
  7878.     if (pred < 0)
  7879.       buf [SLRN_GROUPLENS_DISPLAY_WIDTH / 2] = '?';
  7880.     else
  7881.       {
  7882.          b = buf;
  7883.          while ((pred > 0) && (b < bmax))
  7884.            {
  7885.           pred--;
  7886.           *b++ = '*';
  7887.            }
  7888.       }
  7889.      }
  7890.    slrn_set_color (GROUPLENS_DISPLAY_COLOR);
  7891.    smg_write_nchars (buf, SLRN_GROUPLENS_DISPLAY_WIDTH);
  7892.    slrn_set_color (0);
  7893. }
  7894. #endif
  7895.  
  7896. /*}}}*/
  7897.  
  7898. /*}}}*/
  7899.  
  7900. static char *disp_get_header_subject (Slrn_Header_Type *h, int row)
  7901. {
  7902.    if ((Slrn_Show_Thread_Subject)
  7903.        /* || (0 == h->num_children) */
  7904.        || (h->parent == NULL)
  7905.        || (row == 1)
  7906.        || (row + 1 == SLtt_Screen_Rows)/* at bottom of screen */
  7907.        || check_subject (h))
  7908.      return h->subject;
  7909.  
  7910.    return ">";
  7911. }
  7912.  
  7913. static void disp_draw_thread_tree (Slrn_Header_Type *h)
  7914. {
  7915.    if (0 == (Slrn_Sorting_Mode & SORT_BY_THREADS))
  7916.      return;
  7917.  
  7918.    if ((h->next != NULL)
  7919.        && (h->next->flags & HEADER_HIDDEN))
  7920.      {
  7921.     slrn_set_color (THREAD_NUM_COLOR);
  7922.     SLsmg_printf (" %2d ",
  7923.               1 + h->num_children);
  7924.      }
  7925.    else
  7926.      {
  7927.     smg_write_string ("    ");
  7928.     if ((h->parent != NULL) || (h->flags & FAKE_CHILDREN))
  7929.       draw_tree (h);
  7930.      }
  7931. }
  7932.  
  7933. static int display_header_line (Slrn_Header_Type *h, int row)
  7934. {
  7935.    char *fmt;
  7936.    char ch;
  7937.  
  7938.    SLsmg_gotorc (row, 0);
  7939.    slrn_set_color (0);
  7940.  
  7941.    fmt = Header_Display_Formats[Header_Format_Number];
  7942.  
  7943.    if (fmt == NULL)
  7944.      fmt = "%F%-5S%G%-5l:[%12r]%t%s";
  7945.  
  7946.    while ((ch = *fmt) != 0)
  7947.      {
  7948.     char *s;
  7949.     int len;
  7950.     int color;
  7951.     int field_len;
  7952.     char buf[32];
  7953.     int right_justify;
  7954.     int spaces = 0;
  7955.  
  7956.     fmt++;
  7957.     if (ch != '%')
  7958.       {
  7959.          SLsmg_write_nchars (&ch, 1);
  7960.          continue;
  7961.       }
  7962.  
  7963.     ch = *fmt++;
  7964.  
  7965.     s = NULL;
  7966.     len = -1;
  7967.     color = 0;
  7968.  
  7969.     field_len = -1;
  7970.     if (ch == '-')
  7971.       {
  7972.          right_justify = 1;
  7973.          ch = *fmt++;
  7974.       }
  7975.     else
  7976.       right_justify = 0;
  7977.  
  7978.     if (isdigit (ch))
  7979.       {
  7980.          field_len = 0;
  7981.          do
  7982.            {
  7983.           field_len = 10 * field_len + (int) (ch - '0');
  7984.           ch = *fmt++;
  7985.            }
  7986.          while (isdigit (ch));
  7987.       }
  7988.  
  7989.     switch (ch)
  7990.       {
  7991.        case 0:
  7992.          slrn_set_color (0);
  7993.          SLsmg_erase_eol ();
  7994.          return 0;
  7995.            
  7996.        default:
  7997.          break;
  7998.  
  7999.        case 'F':
  8000.          disp_write_flags (h, row);
  8001.          break;
  8002.          
  8003.          /* score */         
  8004. #if SLRN_HAS_SORT_BY_SCORE
  8005.        case 'S':
  8006.          if (Slrn_Display_Score)
  8007.            {
  8008.           int score;
  8009.           
  8010.           score = ((h->child != NULL) && (h->child->flags & HEADER_HIDDEN)
  8011.                ? h->thread_score : h->score);
  8012.           
  8013.           s = buf;
  8014.           if (score)
  8015.             sprintf (s, "%d", score);
  8016.           else *s = 0;
  8017.            }
  8018.          break;
  8019. #endif
  8020.        case 'l':
  8021.          s = buf;
  8022.          sprintf (s, "%d", h->lines);
  8023.          break;
  8024.  
  8025.        case '%':
  8026.          s = "%";
  8027.          len = 1;
  8028.          break;
  8029.  
  8030.        case 't':
  8031.          disp_draw_thread_tree (h);
  8032.          break;
  8033.  
  8034.        case 'f':
  8035.          color = AUTHOR_COLOR;
  8036.          s = h->from;
  8037.          if (s == NULL) s = "";
  8038.          break;
  8039.        case 'r':
  8040.          color = AUTHOR_COLOR;
  8041.          s = h->realname;
  8042.          len = h->realname_len;
  8043.          if (s == NULL) s = "";
  8044.          break;
  8045.        case 's':
  8046.          color = SUBJECT_COLOR;
  8047.          s = disp_get_header_subject (h, row);
  8048.          break;
  8049. #if SLRN_HAS_GROUPLENS
  8050.        case 'G':
  8051.          disp_write_grplens (h);
  8052.          break;
  8053. #endif
  8054.        case 'd':
  8055.          if (NULL == (s = h->date))
  8056.            s = "";
  8057.          break;
  8058.          
  8059.        case 'n':
  8060.          s = buf;
  8061.          sprintf (s, "%d", h->number);
  8062.          break;
  8063.  
  8064.            case 'g':
  8065.              SLsmg_erase_eol ();
  8066.              if (right_justify)
  8067.                field_len = SLtt_Screen_Cols - field_len;
  8068.              SLsmg_gotorc (row, field_len);
  8069.              break;
  8070.       }
  8071.  
  8072.     slrn_set_color (color);
  8073.     if (s == NULL)
  8074.       continue;
  8075.  
  8076.     if (len == -1) len = strlen (s);
  8077.     if (field_len != -1)
  8078.       {
  8079.          if (field_len > len)
  8080.            spaces = field_len - len;
  8081.          else 
  8082.            len = field_len;
  8083.       }
  8084.     if (right_justify)
  8085.       {
  8086.          while (spaces)
  8087.            {
  8088.           SLsmg_write_nchars (" ", 1);
  8089.           spaces--;
  8090.            }
  8091.       }
  8092.     SLsmg_write_nchars (s, len);
  8093.     while (spaces)
  8094.       {
  8095.          spaces--;
  8096.          SLsmg_write_nchars (" ", 1);
  8097.       }
  8098.     if (color) slrn_set_color (0);
  8099.      }
  8100.  
  8101.    slrn_set_color (0);
  8102.    SLsmg_erase_eol ();
  8103.    return 0;
  8104. }
  8105.  
  8106. static void display_article_line (Slrn_Article_Line_Type *l)
  8107. {
  8108.    char *lbuf = l->buf;
  8109.    
  8110.    if (l->flags & HEADER_LINE)
  8111.      {
  8112.     if ((unsigned char)*lbuf > (unsigned char)' ')
  8113.       {
  8114.          lbuf = slrn_strchr (lbuf, ':');
  8115.          if (lbuf != NULL)
  8116.            {
  8117.           lbuf++;
  8118.           slrn_set_color (SLRN_HEADER_KEYWORD_COLOR);
  8119.           smg_write_nchars (l->buf, lbuf - l->buf);
  8120.            }
  8121.          else lbuf = l->buf;
  8122.       }
  8123.     slrn_set_color (HEADER_COLOR);
  8124.     smg_write_string (lbuf);
  8125.      }
  8126.    else
  8127.      {
  8128.     if (l->flags & QUOTE_LINE)
  8129.       slrn_set_color (QUOTE_COLOR);
  8130.     else if (l->flags & SIGNATURE_LINE)
  8131.       slrn_set_color (SIGNATURE_COLOR);
  8132.     else slrn_set_color (ARTICLE_COLOR);
  8133. #if SLRN_HAS_SPOILERS
  8134.     if (l->flags & SPOILER_LINE)
  8135.       write_spoiler ((unsigned char *) lbuf);
  8136.     else
  8137. #endif
  8138.       if (Do_Rot13) write_rot13 ((unsigned char *) lbuf);
  8139.     else smg_write_string (lbuf);
  8140.      }
  8141. }
  8142.  
  8143.  
  8144. static void update_header_window (void)
  8145. {
  8146.    Slrn_Header_Type *h;
  8147.    int height;
  8148.    int row;
  8149.    int last_cursor_row;
  8150.    int c0;
  8151.  
  8152.    height = Slrn_Header_Window.nrows;
  8153.  
  8154.    h = (Slrn_Header_Type *) Slrn_Header_Window.top_window_line;
  8155.    SLscroll_find_top (&Slrn_Header_Window);
  8156.    if (h != (Slrn_Header_Type *) Slrn_Header_Window.top_window_line)
  8157.      {
  8158.     Slrn_Full_Screen_Update = 1;
  8159.     h = (Slrn_Header_Type *) Slrn_Header_Window.top_window_line;
  8160.      }
  8161.    
  8162.    last_cursor_row = Last_Cursor_Row;
  8163.  
  8164.    SLsmg_gotorc (height + 1, 0);
  8165.    slrn_set_color (STATUS_COLOR);
  8166.    SLsmg_printf ("News Group: %s", Slrn_Current_Group_Name);
  8167.    slrn_print_percent (height + 1, 60, 
  8168.                &Slrn_Header_Window);
  8169.  
  8170.    c0 = Header_Window_HScroll;
  8171.    SLsmg_set_screen_start (NULL, &c0);
  8172.  
  8173.    for (row = 1; row <= height; row++)
  8174.      {
  8175.     while ((h != NULL) && (h->flags & HEADER_HIDDEN))
  8176.       h = h->next;
  8177.     
  8178.     if (Slrn_Full_Screen_Update
  8179.         || (row == last_cursor_row))
  8180.       {
  8181.          if (h == NULL)
  8182.            {
  8183.           SLsmg_gotorc (row, 0);
  8184.           slrn_set_color (0);
  8185.           SLsmg_erase_eol ();
  8186.           continue;
  8187.            }
  8188.  
  8189.          display_header_line (h, row);
  8190.       }
  8191.     h = h->next;
  8192.      }
  8193.    SLsmg_set_screen_start (NULL, NULL);
  8194. }
  8195.  
  8196. static void update_article_window (void)
  8197. {
  8198.    Slrn_Article_Line_Type *l;
  8199.  
  8200.    if (Article_Visible == 0)
  8201.      return;
  8202.  
  8203.    l = (Slrn_Article_Line_Type *) Slrn_Article_Window.top_window_line;
  8204.  
  8205.    SLscroll_find_top (&Slrn_Article_Window);
  8206.  
  8207.    if (Slrn_Full_Screen_Update 
  8208.        || (l != Article_Current_Line)
  8209.        || (l != (Slrn_Article_Line_Type *) Slrn_Article_Window.top_window_line))
  8210.      {
  8211.     int row;
  8212.     int c0;
  8213.     int height;
  8214.  
  8215.     height = SLtt_Screen_Rows - 2;
  8216.  
  8217.     SLsmg_gotorc (height, 0);
  8218.     slrn_set_color (STATUS_COLOR);
  8219.     if (Article_Window_HScroll) smg_write_char ('<'); else smg_write_char (' ');
  8220.     SLsmg_printf (" %d : ", Header_Showing->number);
  8221.     SLsmg_write_string (Header_Showing->subject);
  8222.     slrn_print_percent (height, 60, &Slrn_Article_Window);
  8223.     slrn_set_color (0);
  8224.     
  8225.     c0 = Article_Window_HScroll;
  8226.     SLsmg_set_screen_start (NULL, &c0);
  8227.       l = Article_Current_Line;
  8228.     
  8229.     row = Slrn_Header_Window.nrows + 2;
  8230.     if (row == 2) row--;           /* header window not visible */
  8231.  
  8232.     while (row < height)
  8233.       {
  8234.          SLsmg_gotorc (row, 0);
  8235.          
  8236.          if (l != NULL) 
  8237.            {
  8238.           if (l->flags & HIDDEN_LINE)
  8239.             {
  8240.                l = l->next;
  8241.                continue;
  8242.             }
  8243.           
  8244.           display_article_line (l);
  8245.           l = l->next;
  8246.            }
  8247. #if SLRN_HAS_TILDE_FEATURE
  8248.          else if (Slrn_Use_Tildes)
  8249.            {
  8250.           slrn_set_color (SLRN_TILDE_COLOR);
  8251.           smg_write_char ('~');
  8252.            }
  8253. #endif
  8254.  
  8255.          slrn_set_color (0);
  8256.          SLsmg_erase_eol ();
  8257.          
  8258.          row++;
  8259.       }
  8260.     
  8261.     if (((l == NULL) 
  8262.          || ((l->flags & SIGNATURE_LINE) && Slrn_Sig_Is_End_Of_Article))
  8263.         && (Slrn_Current_Header == Header_Showing))
  8264.       At_End_Of_Article = Slrn_Current_Header;
  8265.     
  8266.     SLsmg_set_screen_start (NULL, NULL);
  8267.      }
  8268. }
  8269.  
  8270. static void art_update_screen (void) /*{{{*/
  8271. {
  8272.    At_End_Of_Article = NULL;
  8273. #if SLRN_HAS_SPOILERS
  8274.    Spoilers_Visible = NULL;
  8275. #endif
  8276.    if (Slrn_Full_Screen_Update) Largest_Header_Number = 0;
  8277.  
  8278.    update_header_window ();
  8279.    update_article_window ();
  8280.  
  8281.    if (Slrn_Use_Mouse) slrn_update_article_menu ();
  8282.    else
  8283.      slrn_update_top_status_line ();
  8284.  
  8285.    if (Slrn_Message_Present == 0) 
  8286.      {
  8287. #if SLRN_HAS_SPOILERS
  8288.     if (Spoilers_Visible != NULL)
  8289.       slrn_message ("Spoilers visible!");
  8290.     else
  8291. #endif
  8292. #if SLRN_HAS_END_OF_THREAD
  8293.       if (Article_Visible
  8294.           && (-1 != display_end_of_thread (Slrn_Current_Header)))
  8295.         /* do nothing */ ;
  8296.     else
  8297. #endif
  8298.       quick_help ();
  8299.      }
  8300.  
  8301.    Last_Cursor_Row = 1 + (int) Slrn_Header_Window.window_row;
  8302.  
  8303.    if ((Header_Window_Nrows == 0)
  8304.        && Article_Visible)
  8305.      {
  8306.     if (Slrn_Message_Present == 0)
  8307.       display_header_line (Slrn_Current_Header, SLtt_Screen_Rows - 1);
  8308.  
  8309.     SLsmg_gotorc (SLtt_Screen_Rows - 1, 0);
  8310.     Slrn_Full_Screen_Update = 0;
  8311.     return;
  8312.      }
  8313.  
  8314.    if (Header_Window_HScroll)
  8315.      {
  8316.     int c0 = Header_Window_HScroll;
  8317.     SLsmg_set_screen_start (NULL, &c0);
  8318.      }
  8319.    display_header_line (Slrn_Current_Header, Last_Cursor_Row);
  8320.    SLsmg_set_screen_start (NULL, NULL);
  8321.  
  8322.    SLsmg_gotorc (Last_Cursor_Row, 0);
  8323.  
  8324.    slrn_set_color (CURSOR_COLOR);
  8325. #if SLANG_VERSION > 10003
  8326.    if (Slrn_Display_Cursor_Bar)
  8327.      SLsmg_set_color_in_region (CURSOR_COLOR, Last_Cursor_Row, 0, 1, SLtt_Screen_Cols);
  8328.    else
  8329. #endif
  8330.    smg_write_string ("->");
  8331.  
  8332.    slrn_set_color (0);
  8333.    
  8334.    Slrn_Full_Screen_Update = 0;
  8335. }
  8336.  
  8337. /*}}}*/
  8338.  
  8339.  
  8340. /*}}}*/
  8341.  
  8342.